awshark 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Awshark
4
+ module Rds
5
+ class CheckReservations
6
+ attr_reader :instances, :reservations
7
+
8
+ def initialize(instances:, reservations:)
9
+ @instances = instances
10
+ @reservations = reservations
11
+ end
12
+
13
+ def type_permutations
14
+ type_permutations_hash = {}
15
+
16
+ (reservations + instances).each do |instance|
17
+ key = "#{instance.type}+#{instance.multi_az}"
18
+ type_permutations_hash[key] = {
19
+ type: instance.type,
20
+ multi_az: instance.multi_az
21
+ }
22
+ end
23
+
24
+ type_permutations_hash.values
25
+ end
26
+
27
+ def check
28
+ type_permutations.map do |permutation|
29
+ type = permutation[:type]
30
+ multi_az = permutation[:multi_az]
31
+
32
+ instance_count = count_instances(type, multi_az)
33
+ reserved_count = count_reservations(type, multi_az)
34
+
35
+ OpenStruct.new(
36
+ type: type,
37
+ multi_az: multi_az,
38
+ instance_count: instance_count,
39
+ reserved_count: reserved_count
40
+ )
41
+ end
42
+ end
43
+
44
+ def count_instances(type, multi_az)
45
+ instances.select do |i|
46
+ i.type == type && i.multi_az == multi_az
47
+ end.size
48
+ end
49
+
50
+ def count_reservations(type, multi_az)
51
+ reservation = reservations.detect do |r|
52
+ r.type == type && r.multi_az == multi_az
53
+ end
54
+
55
+ reservation ? reservation.count : 0
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ # TODO: also work for DB clusters
4
+
5
+ module Awshark
6
+ module Rds
7
+ class Manager
8
+ def instances
9
+ return @instances if defined?(@instances)
10
+
11
+ @instances = []
12
+ response = client.describe_db_instances
13
+
14
+ response[:db_instances].each do |instance|
15
+ @instances << OpenStruct.new(
16
+ name: instance[:db_instance_identifier],
17
+ type: instance[:db_instance_class],
18
+ state: instance[:db_instance_status],
19
+ multi_az: instance[:multi_az],
20
+ engine: instance[:engine],
21
+ engine_version: instance[:engine_version],
22
+ encrypted: instance[:storage_encrypted],
23
+ storage_type: instance[:storage_type]
24
+ )
25
+ end
26
+
27
+ @instances
28
+ end
29
+
30
+ def reservations
31
+ return @reservations if defined?(@reservations)
32
+
33
+ response = client.describe_reserved_db_instances
34
+
35
+ @reservations = response[:reserved_db_instances].map do |instance|
36
+ OpenStruct.new(
37
+ count: instance[:db_instance_count],
38
+ type: instance[:db_instance_class],
39
+ multi_az: instance[:multi_az],
40
+ state: instance[:state],
41
+ offering_type: instance[:offering_type]
42
+ )
43
+ end
44
+ @reservations.select! { |ri| ri[:state] == 'active' }
45
+
46
+ @reservations
47
+ end
48
+
49
+ def check_reservations
50
+ CheckReservations.new(instances: instances, reservations: reservations).check
51
+ end
52
+
53
+ private
54
+
55
+ def client
56
+ @client ||= Aws::RDS::Client.new(
57
+ region: Aws.config[:region] || 'eu-central-1'
58
+ )
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Awshark
4
+ module S3
5
+ class Artifact
6
+ attr_reader :key
7
+
8
+ def initialize(key)
9
+ @key = key
10
+ end
11
+
12
+ def cache_control
13
+ return "public, max-age=#{1.year.to_i}, s-maxage=#{1.year.to_i}" if fingerprint?
14
+
15
+ 'public, max-age=0, s-maxage=120, must-revalidate'
16
+ end
17
+
18
+ def fingerprint?
19
+ return false if key.blank?
20
+
21
+ basename.match(/\.([0-9a-f]{20}|[0-9a-f]{32})\./).present?
22
+ end
23
+
24
+ def content_type
25
+ mime = MiniMime.lookup_by_filename(basename)
26
+ if mime
27
+ mime.content_type
28
+ else
29
+ 'application/octet-stream'
30
+ end
31
+ end
32
+
33
+ def basename
34
+ ::File.basename(key)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Awshark
4
+ module S3
5
+ class Bucket
6
+ attr_reader :name, :creation_date, :region
7
+
8
+ def initialize(attributes)
9
+ @name = attributes.name
10
+ @creation_date = attributes.creation_date
11
+ @region = attributes.region
12
+
13
+ # fixes S3 quirks
14
+ @region = 'eu-west-1' if @region == 'EU'
15
+ end
16
+
17
+ def byte_size
18
+ metric_value(metric_name: 'BucketSizeBytes', storage_type: 'StandardStorage')
19
+ end
20
+
21
+ def number_of_objects
22
+ metric_value(metric_name: 'NumberOfObjects', storage_type: 'AllStorageTypes')
23
+ end
24
+
25
+ private
26
+
27
+ def cloudwatch
28
+ @cloudwatch ||= Aws::CloudWatch::Client.new(region: region)
29
+ end
30
+
31
+ def metric_value(metric_name:, storage_type:)
32
+ return 0 unless region.present?
33
+
34
+ response = cloudwatch.get_metric_statistics(
35
+ namespace: 'AWS/S3',
36
+ metric_name: metric_name,
37
+ dimensions: [
38
+ {
39
+ name: 'BucketName',
40
+ value: name
41
+ },
42
+ {
43
+ name: 'StorageType',
44
+ value: storage_type
45
+ }
46
+ ],
47
+ start_time: Time.now - 7.days,
48
+ end_time: Time.now,
49
+ period: 86_400,
50
+ statistics: ['Average']
51
+ )
52
+ return 0 if response.datapoints.empty?
53
+
54
+ sorted_datapoints = response.datapoints.sort_by(&:timestamp)
55
+ sorted_datapoints.last.average
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Awshark
4
+ module S3
5
+ class Manager
6
+ def list_buckets
7
+ response = client.list_buckets
8
+ response.buckets.map do |bucket|
9
+ attributes = OpenStruct.new(bucket.to_hash)
10
+ location = client.get_bucket_location(bucket: bucket.name)
11
+ attributes.region = location.location_constraint
12
+ Awshark::S3::Bucket.new(attributes)
13
+ end
14
+ end
15
+
16
+ def list_objects(bucket:, prefix: nil)
17
+ objects = []
18
+ response = client.list_objects_v2(bucket: bucket, prefix: prefix)
19
+ objects.concat(response.contents)
20
+
21
+ while response.next_continuation_token
22
+ response = client.list_objects_v2(
23
+ bucket: bucket,
24
+ prefix: prefix,
25
+ continuation_token: response.next_continuation_token
26
+ )
27
+ objects.concat(response.contents)
28
+ end
29
+
30
+ objects.select { |o| o.size.positive? }
31
+ end
32
+
33
+ def update_object_metadata(bucket, key, options = {})
34
+ raise ArgumentError, 'meta=acl:STRING is missing' if options[:acl].blank?
35
+
36
+ object = client.get_object(bucket: bucket, key: key)
37
+ metadata = object.metadata.merge(options[:metadata] || {})
38
+ artifact = Artifact.new(key)
39
+
40
+ # copy object in place to update metadata
41
+ client.copy_object(
42
+ acl: options[:acl] || 'private',
43
+ bucket: bucket,
44
+ copy_source: "/#{bucket}/#{key}",
45
+ key: key,
46
+ cache_control: options[:cache_control] || object.cache_control,
47
+ content_type: artifact.content_type,
48
+ metadata: metadata.stringify_keys,
49
+ metadata_directive: 'REPLACE',
50
+ server_side_encryption: 'AES256'
51
+ )
52
+ end
53
+
54
+ private
55
+
56
+ def client
57
+ @client ||= Aws::S3::Client.new(
58
+ region: Aws.config[:region] || 'eu-central-1',
59
+ signature_version: 'v4'
60
+ )
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Awshark
4
+ module Subcommands
5
+ module ClassOptions
6
+ def process_class_options
7
+ command = current_command_chain.last
8
+ cli_options = options.merge(parent_options || {}).symbolize_keys
9
+
10
+ if cli_options[:help]
11
+ respond_to?(command) ? help(command) : help
12
+ exit(0)
13
+ end
14
+
15
+ setup_aws_credentials(options)
16
+ end
17
+
18
+ private
19
+
20
+ def setup_aws_credentials(options)
21
+ profile_resolver = ProfileResolver.new(options)
22
+
23
+ ::Aws.config[:region] = profile_resolver.region
24
+ ::Aws.config[:credentials] = profile_resolver.credentials
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aws-sdk-cloudformation'
4
+ require 'diffy'
5
+ require 'recursive-open-struct'
6
+
7
+ require 'awshark/cloud_formation/file_loading'
8
+ require 'awshark/cloud_formation/inferrer'
9
+ require 'awshark/cloud_formation/manager'
10
+ require 'awshark/cloud_formation/parameters'
11
+ require 'awshark/cloud_formation/stack'
12
+ require 'awshark/cloud_formation/stack_events'
13
+ require 'awshark/cloud_formation/template'
14
+
15
+ module Awshark
16
+ module Subcommands
17
+ class CloudFormation < Thor
18
+ include Awshark::Subcommands::ClassOptions
19
+
20
+ class_option :bucket, type: :string, desc: 'S3 bucket for template'
21
+ class_option :iam, type: :boolean, desc: 'Needs IAM capabilities'
22
+ class_option :stage, type: :string, desc: 'Stage of the configuration'
23
+
24
+ desc 'deploy', 'Updates or creates an AWS CloudFormation stack'
25
+ long_desc <<-LONGDESC
26
+ Updates or creates a CloudFormation stack on AWS.
27
+
28
+ awshark cf deploy TEMPLATE_PATH CAPABILITIES
29
+
30
+ Examples:
31
+
32
+ awshark cf deploy foo_template
33
+
34
+ awshark cf deploy iam_template IAM
35
+ LONGDESC
36
+ def deploy(template_path)
37
+ process_class_options
38
+
39
+ manager = create_manager(template_path)
40
+ print_stack_information(manager.stack)
41
+
42
+ manager.update_stack
43
+ sleep(2)
44
+ manager.tail_stack_events
45
+ rescue GracefulFail => e
46
+ puts e.message
47
+ end
48
+
49
+ desc 'diff', 'Show diff between local stack template and AWS CloudFormation'
50
+ long_desc <<-LONGDESC
51
+ Shows colored diff between local stack template and AWS CloudFormation
52
+
53
+ Example: `awshark cf diff TEMPLATE_PATH`
54
+ LONGDESC
55
+ def diff(template_path)
56
+ process_class_options
57
+
58
+ manager = create_manager(template_path)
59
+ print_stack_information(manager.stack)
60
+
61
+ diff = manager.diff_stack_template
62
+ puts diff
63
+ end
64
+
65
+ private
66
+
67
+ def create_manager(template_path)
68
+ Awshark::CloudFormation::Manager.new(template_path, options.symbolize_keys)
69
+ end
70
+
71
+ def print_stack_information(stack)
72
+ if stack.exists?
73
+ args = { name: stack.name, created_at: stack.creation_time }
74
+ printf "Stack: %-20<name>s (created at %<created_at>s)\n\n", args
75
+ else
76
+ printf "Stack: %<name>s\n\n", name: stack.name
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aws-sdk-ec2'
4
+
5
+ require 'awshark/ec2/instance'
6
+ require 'awshark/ec2/manager'
7
+
8
+ module Awshark
9
+ module Subcommands
10
+ class EC2 < Thor
11
+ include Awshark::Subcommands::ClassOptions
12
+
13
+ desc 'list', 'List all EC2 instances'
14
+ long_desc <<-LONGDESC
15
+ List all EC2 instances in a region
16
+
17
+ Example: `awshark ec2 list STATE`
18
+ LONGDESC
19
+ def list(state = 'running')
20
+ process_class_options
21
+
22
+ instances = manager.public_send("#{state}_instances")
23
+ instances = instances.sort_by(&:name)
24
+
25
+ instances.each do |i|
26
+ args = { name: i.name, type: i.type, public_dns: i.public_dns_name, state: i.state }
27
+ printf "%-40<name>s %-12<type>s %-60<public_dns>s %<state>s\n", args
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def manager
34
+ @manager ||= Awshark::EC2::Manager.new
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aws-sdk-ecr'
4
+
5
+ module Awshark
6
+ module Subcommands
7
+ class Ecs < Thor
8
+ include Awshark::Subcommands::ClassOptions
9
+
10
+ desc 'login', 'docker login to AWS ECR'
11
+ long_desc <<-LONGDESC
12
+ Use docker login with AWS credentials to Elastic Container Registry
13
+
14
+ Example: `awshark ecs login`
15
+ LONGDESC
16
+ def login
17
+ response = client.get_authorization_token
18
+ token = Base64.decode64(response.authorization_data.first.authorization_token)
19
+
20
+ user_name = token.split(':').first
21
+ password = token.split(':').last
22
+ url = "https://#{Awshark.config.aws_account_id}.dkr.ecr.eu-central-1.amazonaws.com"
23
+
24
+ `docker login -u #{user_name} -p #{password} #{url}`
25
+ end
26
+
27
+ private
28
+
29
+ def client
30
+ @client ||= Aws::ECR::Client.new(region: region)
31
+ end
32
+
33
+ def region
34
+ Aws.config[:region] || 'eu-central-1'
35
+ end
36
+ end
37
+ end
38
+ end