awshark 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Awshark
4
+ module CloudFormation
5
+ class Parameters
6
+ include FileLoading
7
+
8
+ attr_reader :stage
9
+
10
+ def initialize(path:, stage: nil)
11
+ @filepath = Dir.glob("#{path}/parameters.*").detect do |f|
12
+ %w[.json .yml .yaml].include?(File.extname(f))
13
+ end
14
+ @stage = stage
15
+ end
16
+
17
+ def params
18
+ @params ||= load_parameters(@filepath)
19
+ end
20
+
21
+ def to_hash
22
+ params
23
+ end
24
+
25
+ def stack_parameters
26
+ params.each.map do |k, v|
27
+ {
28
+ parameter_key: k,
29
+ parameter_value: v
30
+ }
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def load_parameters(filepath)
37
+ data = load_file(filepath) || {}
38
+
39
+ data[stage] || data
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ #
6
+ # https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/CloudFormation/Types/Stack.html
7
+ #
8
+ module Awshark
9
+ module CloudFormation
10
+ class Stack
11
+ extend Forwardable
12
+
13
+ attr_reader :name
14
+
15
+ def_delegators :@stack,
16
+ :stack_id,
17
+ :stack_name,
18
+ :description,
19
+ :parameters,
20
+ :creation_time,
21
+ :deletion_time,
22
+ :last_updated_time,
23
+ :stack_status,
24
+ :stack_status_reason,
25
+ :notification_arns,
26
+ :capabilities,
27
+ :outputs,
28
+ :role_arn,
29
+ :tags
30
+
31
+ def initialize(name:)
32
+ @name = name
33
+ @stack = get_stack(name)
34
+ end
35
+
36
+ # @return [Boolean]
37
+ def exists?
38
+ @stack.present?
39
+ end
40
+
41
+ def events
42
+ response = client.describe_stack_events(stack_name: stack_id)
43
+ response.stack_events
44
+ end
45
+
46
+ def create_or_update(params)
47
+ if exists?
48
+ client.update_stack(params)
49
+ else
50
+ client.create_stack(params)
51
+ end
52
+ end
53
+
54
+ def reload
55
+ @stack = get_stack(name)
56
+
57
+ self
58
+ end
59
+
60
+ # @return [Hash]
61
+ def template
62
+ return nil unless exists?
63
+ return @template if @template.present?
64
+
65
+ response = client.get_template(stack_name: stack_id)
66
+ @template = response.template_body
67
+ end
68
+
69
+ private
70
+
71
+ def client
72
+ return Awshark.config.cloud_formation.client if Awshark.config.cloud_formation.client
73
+
74
+ region = Aws.config[:region]
75
+ @client ||= Aws::CloudFormation::Client.new(region: region)
76
+ end
77
+
78
+ def get_stack(stack_name)
79
+ response = begin
80
+ client.describe_stacks(stack_name: stack_name)
81
+ rescue Aws::CloudFormation::Errors::ValidationError
82
+ @stack = nil
83
+ return
84
+ end
85
+
86
+ if response.stacks.length > 1
87
+ raise ArgumentError, "Found too many stacks with name #{stack_name}. There should only be one."
88
+ end
89
+
90
+ response.stacks[0]
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Awshark
4
+ module CloudFormation
5
+ class StackEvents
6
+ attr_reader :stack
7
+ attr_reader :start_time, :last_event
8
+
9
+ def initialize(stack)
10
+ @stack = stack
11
+ @start_time = Time.now - 10
12
+ end
13
+
14
+ def done?
15
+ return false if last_event.nil?
16
+ return false if last_event.resource_type != 'AWS::CloudFormation::Stack'
17
+
18
+ last_event.resource_status.match(/IN_PROGRESS/).nil?
19
+ end
20
+
21
+ def new_events
22
+ events = stack.events
23
+
24
+ return [] if events.blank?
25
+
26
+ if last_event.nil?
27
+ @last_event = events.first
28
+ new_events = events.select { |e| e.timestamp > start_time }
29
+ return new_events
30
+ end
31
+
32
+ return [] unless new_events?(events)
33
+
34
+ last_event_index = events.index { |e| e.event_id == last_event.event_id }
35
+ new_events = events[0..last_event_index - 1]
36
+ @last_event = new_events.first
37
+
38
+ new_events
39
+ end
40
+
41
+ def new_events?(events)
42
+ events.first.event_id != last_event.event_id
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Awshark
4
+ module CloudFormation
5
+ class Template
6
+ include FileLoading
7
+
8
+ attr_reader :path
9
+ attr_reader :bucket, :name, :stage
10
+
11
+ def initialize(path, options = {})
12
+ @path = path
13
+
14
+ @bucket = options[:bucket]
15
+ @name = options[:name]
16
+ @stage = options[:stage]
17
+ end
18
+
19
+ # @returns [Hash]
20
+ def as_json
21
+ load_file(template_path, context)
22
+ end
23
+
24
+ # @returns [String]
25
+ def body
26
+ JSON.pretty_generate(as_json)
27
+ end
28
+
29
+ # @returns [Hash]
30
+ def context
31
+ @context ||= begin
32
+ context = load_file(context_path) || {}
33
+ context = context[stage] if context.key?(stage)
34
+
35
+ {
36
+ context: RecursiveOpenStruct.new(context),
37
+ aws_account_id: Awshark.config.aws_account_id,
38
+ stage: stage
39
+ }
40
+ end
41
+ end
42
+
43
+ # @returns [Integer]
44
+ def size
45
+ body.size
46
+ end
47
+
48
+ # @returns [Boolean]
49
+ def uploaded?
50
+ @uploaded == true
51
+ end
52
+
53
+ # @returns [String]
54
+ def url
55
+ upload unless uploaded?
56
+
57
+ "https://#{bucket}.s3.#{region}.amazonaws.com/#{s3_key}"
58
+ end
59
+
60
+ private
61
+
62
+ def region
63
+ Aws.config[:region] || 'eu-central-1'
64
+ end
65
+
66
+ def s3
67
+ return Awshark.config.s3.client if Awshark.config.s3.client
68
+
69
+ @s3 ||= Aws::S3::Client.new(region: region, signature_version: 'v4')
70
+ end
71
+
72
+ def s3_key
73
+ # https://apidock.com/ruby/Time/strftime
74
+ @s3_key ||= "awshark/#{name}/#{Time.now.strftime('%Y-%m-%d')}.json"
75
+ end
76
+
77
+ def upload
78
+ raise ArgumentError, 'Bucket for template upload to S3 is missing' if bucket.blank?
79
+
80
+ Awshark.logger.debug "[awshark] Uploading CF template to #{bucket}"
81
+
82
+ s3.put_object(bucket: bucket, key: s3_key, body: body)
83
+ end
84
+
85
+ def context_path
86
+ Dir.glob("#{path}/context.*").detect do |f|
87
+ %w[.json .yml .yaml].include?(File.extname(f))
88
+ end
89
+ end
90
+
91
+ def template_path
92
+ @template_path ||= if File.directory?(path)
93
+ Dir.glob("#{path}/template.*").detect do |f|
94
+ %w[.json .yml .yaml].include?(File.extname(f))
95
+ end
96
+ else
97
+ path
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Awshark
4
+ class Configuration
5
+ attr_accessor :cloud_formation, :s3
6
+
7
+ def initialize
8
+ @cloud_formation = OpenStruct.new(
9
+ client: nil,
10
+ event_polling: 3
11
+ )
12
+
13
+ @s3 = OpenStruct.new(
14
+ client: nil
15
+ )
16
+ end
17
+
18
+ def aws_account_id
19
+ return @aws_account_id if defined?(@aws_account_id)
20
+
21
+ response = sts_client.get_caller_identity
22
+
23
+ @aws_account_id = response.account
24
+ end
25
+
26
+ def sts_client
27
+ @sts_client ||= Aws::STS::Client.new
28
+ end
29
+
30
+ private
31
+
32
+ attr_writer :sts_client
33
+ end
34
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Awshark
4
+ module EC2
5
+ class Instance
6
+ attr_reader :id
7
+ attr_reader :type
8
+ attr_reader :state
9
+ attr_reader :private_ip_address
10
+ attr_reader :public_ip_address
11
+ attr_reader :public_dns_name
12
+ attr_reader :vpc_id
13
+ attr_reader :subnet_id
14
+ attr_reader :tags
15
+
16
+ def initialize(instance)
17
+ @id = instance.instance_id
18
+ @type = instance.instance_type
19
+ @state = instance.state.name
20
+ @private_ip_address = instance.private_ip_address
21
+ @public_ip_address = instance.public_ip_address
22
+ @public_dns_name = instance.public_dns_name
23
+ @vpc_id = instance.vpc_id
24
+ @subnet_id = instance.subnet_id
25
+ @tags = instance.tags
26
+ end
27
+
28
+ def name
29
+ tag_value(tags, 'Name').split(' - ').last
30
+ rescue StandardError
31
+ 'null'
32
+ end
33
+
34
+ private
35
+
36
+ def tag_value(tags, key)
37
+ return nil if tags.empty?
38
+
39
+ tag = tags.detect { |t| t.key == key }
40
+ return tag.value if tag
41
+
42
+ 'null'
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Awshark
4
+ module EC2
5
+ class Manager
6
+ def all_instances
7
+ return @all_instances if defined?(@all_instances)
8
+
9
+ @all_instances = []
10
+ response = client.describe_instances
11
+
12
+ response.each_page do |page|
13
+ page.reservations.each do |reservation|
14
+ reservation.instances.each do |instance|
15
+ @all_instances << Instance.new(instance)
16
+ end
17
+ end
18
+ end
19
+
20
+ @all_instances
21
+ end
22
+
23
+ %i[running stopped terminated].each do |state|
24
+ define_method "#{state}_instances" do
25
+ all_instances.select { |i| i.state == state.to_s }
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def client
32
+ @client ||= Aws::EC2::Client.new(region: region)
33
+ end
34
+
35
+ def region
36
+ Aws.config[:region] || 'eu-central-1'
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Awshark
4
+ class ProfileResolver
5
+ attr_reader :region
6
+
7
+ def initialize(options)
8
+ @profile = options[:profile] || ENV['AWS_PROFILE']
9
+ @shared_config = ::Aws::SharedConfig.new(
10
+ profile_name: @profile,
11
+ config_enabled: true
12
+ )
13
+ @region = options[:region] || @shared_config.region || 'eu-central-1'
14
+ end
15
+
16
+ def credentials
17
+ user_credentials || role_credentials
18
+ end
19
+
20
+ # Returns Aws credentials for configuration with
21
+ # [profile]
22
+ # aws_access_key_id=AWS_ACCESS_KEY_ID
23
+ # aws_secret_access_key=AWS_SECRET_ACCESS_KEY
24
+ #
25
+ # Returns nil for configuration with
26
+ # [profile]
27
+ # role_arn = ROLE_ARN
28
+ # source_profile = SOURCE_PROFILE
29
+ #
30
+ # @returns [Aws::Credentials]
31
+ def user_credentials
32
+ @shared_config.credentials
33
+ end
34
+
35
+ # Returns Aws credentials for configuration with
36
+ # [profile]
37
+ # role_arn = ROLE_ARN
38
+ # source_profile = SOURCE_PROFILE
39
+ #
40
+ # @throws [Aws::STS::Errors::AccessDenied] if MultiFactorAuthentication failed
41
+ # @returns [Aws::Credentials]
42
+ def role_credentials
43
+ @shared_config.assume_role_credentials_from_config(
44
+ region: region,
45
+ token_code: mfa_token
46
+ )
47
+ end
48
+
49
+ def mfa_token
50
+ print %(Please enter MFA token for AWS account #{@profile}: )
51
+ $stdin.gets.strip
52
+ end
53
+ end
54
+ end