hibernate 0.1.1 → 0.1.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 71a95bed5e00b8b00d0ec93a5f5f3e00faee39b1de4bc2e5f3616c6358a4c98b
4
- data.tar.gz: ba6cee2ff815f36090f51e802f361079d486bce36f04ba79eee6f719c32a3437
3
+ metadata.gz: 21e7dde6cd66464fa7aff8bc01d5f1338e65ba5f180d27c6d7fc176fcf9dbff5
4
+ data.tar.gz: 84a9f6eb6fd06373cbe68b686048107636cb2821eee5284b9a408062378e05f8
5
5
  SHA512:
6
- metadata.gz: 57e1625b9990bfd59c947a8f894495be4b0d1e5ed9e6601d3e84ee67bc1d8cb760111d917211b8f68b4fa6b4db6d491f621c168679ded265415edf4010ab97cd
7
- data.tar.gz: 1cbe3c34db01b4235f31544ad87452cd722d165b47bf73a50506ccea0b0b1e083c0adde28a70bf84d1b03564ca2860b8f42a39fecab886c7d0f927635be65a89
6
+ metadata.gz: 250624e8f6984ca9cee0685c8d99a990e6fb45c66a88a16a9fd1bc68f430d252c1377d55ca31f1b95806250748e1b8be683fef73f374e1dc708bbba77b65f8e0
7
+ data.tar.gz: 5495ddec8a33acf9c2b4a8eeb8026f762d8f37716912bb238d6c797587403e62f0bb8023202d1755b522f6e09292146bc298e97e1bd335febed73124d54a7658
data/bin/hibernate CHANGED
@@ -12,28 +12,57 @@ class HibernateCLI
12
12
  if ARGV.include?('setup')
13
13
  command = :setup
14
14
  ARGV.delete('setup')
15
- elsif ARGV.include?('node')
15
+ elsif ARGV.include?('rule')
16
16
  command = :manage_ec2
17
- ARGV.delete('node')
18
- elsif ARGV.include?('remove')
19
- command = :remove_ec2
20
- ARGV.delete('remove')
17
+ ARGV.delete('rule')
21
18
  end
22
19
 
23
20
  parser = OptionParser.new do |parser|
24
21
  parser.banner = "Usage: hibernate [command] [options]"
25
22
 
26
- parser.on('--in=<INSTANCE_NAME>', 'Specify the EC2 instance name') do |instance_name|
23
+ parser.on('--instance-name=<INSTANCE_NAME>', 'Specify the EC2 instance name') do |instance_name|
27
24
  options[:instance_name] = instance_name
28
25
  end
29
26
 
30
- parser.on('--start_instance=<START_CRON>', 'Specify the cron expression to start the instance') do |start_cron|
27
+ parser.on('--start-expression=<START_CRON>', 'Specify the cron expression to start the instance') do |start_cron|
31
28
  options[:start_cron] = start_cron
32
29
  end
33
30
 
34
- parser.on('--stop_instance=<STOP_CRON>', 'Specify the cron expression to stop the instance') do |stop_cron|
31
+ parser.on('--stop-expression=<STOP_CRON>', 'Specify the cron expression to stop the instance') do |stop_cron|
35
32
  options[:stop_cron] = stop_cron
36
33
  end
34
+
35
+ parser.on('--start-instance=<true/false>', 'Filter start rules') do |start_instance|
36
+ options[:start_instance] = start_instance == 'true'
37
+ end
38
+
39
+ parser.on('--stop-instance=<true/false>', 'Filter stop rules') do |stop_instance|
40
+ options[:stop_instance] = stop_instance == 'true'
41
+ end
42
+
43
+ parser.on('--rule-name=<RULE Name>', 'Specify the rule name to remove or update') do |rule_name|
44
+ options[:rule_name] = rule_name
45
+ end
46
+
47
+ parser.on('--update', 'Update an existing rule') do
48
+ options[:update] = true
49
+ end
50
+
51
+ parser.on('--remove', 'Remove an existing rule') do
52
+ options[:remove] = true
53
+ end
54
+
55
+ parser.on('--create', 'Create a new rule') do
56
+ options[:create] = true
57
+ end
58
+
59
+ parser.on('--list', 'List rules') do
60
+ options[:list] = true
61
+ end
62
+
63
+ parser.on('--state=<enable/disable>', 'Set rule state to either enable or disable') do |state|
64
+ options[:state] = state
65
+ end
37
66
  end
38
67
 
39
68
  begin
@@ -54,9 +83,15 @@ class HibernateCLI
54
83
  when :setup
55
84
  create_lambda_function
56
85
  when :manage_ec2
57
- manage_ec2_command(options)
58
- when :remove_ec2
59
- remove_ec2_command(options)
86
+ if options[:update]
87
+ update_ec2_rule_command(options)
88
+ elsif options[:remove]
89
+ remove_ec2_rule_command(options)
90
+ elsif options[:create]
91
+ create_ec2_rule_command(options)
92
+ elsif options[:list]
93
+ list_ec2_rule_command(options)
94
+ end
60
95
  end
61
96
  end
62
97
 
@@ -64,25 +99,50 @@ class HibernateCLI
64
99
  LambdaSetup.new.run
65
100
  end
66
101
 
67
- def self.manage_ec2_command(options)
102
+ def self.create_ec2_rule_command(options)
68
103
  if options[:instance_name].nil? || (options[:start_cron].nil? && options[:stop_cron].nil?)
69
104
  puts "Please provide the instance name, and at least one cron expression (start or stop)."
70
- puts "Usage: hibernate node --in=<INSTANCE_NAME> --start_instance=<START_CRON> --stop_instance=<STOP_CRON>"
105
+ puts "Usage: hibernate rule --create --instance-name=<INSTANCE_NAME> --start-expression=<START_CRON> --stop-expression=<STOP_CRON>"
71
106
  exit
72
107
  else
73
- ec2_manager = EC2Manager.new(options[:instance_name], options[:start_cron], options[:stop_cron])
74
- ec2_manager.create_event_rule
108
+ ec2_manager = EC2Manager.new(options[:instance_name])
109
+ ec2_manager.create_event_rule(options[:start_cron], options[:stop_cron])
75
110
  end
76
111
  end
77
112
 
78
- def self.remove_ec2_command(options)
79
- if options[:instance_name].nil? || (options[:start_cron].nil? && options[:stop_cron].nil?)
80
- puts "Please provide the instance name, and at least one cron expression (start or stop) to remove."
81
- puts "Usage: hibernate remove --in=<INSTANCE_NAME> --start_instance=<START_CRON> --stop_instance=<STOP_CRON>"
113
+ def self.remove_ec2_rule_command(options)
114
+ if options[:rule_name].nil?
115
+ puts "Please provide the rule name to remove."
116
+ puts "Usage: hibernate rule --remove --rule-name=<RULE NAME>"
117
+ exit
118
+ else
119
+ ec2_manager = EC2Manager.new(options[:instance_name])
120
+ ec2_manager.remove_event_rule(options[:rule_name])
121
+ end
122
+ end
123
+
124
+ def self.list_ec2_rule_command(options)
125
+ ec2_manager = EC2Manager.new(options[:instance_name])
126
+ ec2_manager.list_event_rules(options)
127
+ end
128
+
129
+ def self.update_ec2_rule_command(options)
130
+ if options[:rule_name].nil?
131
+ puts "Please provide the rule name to update."
132
+ puts "Usage: hibernate rule --update --rule-name=<RULE_NAME> [--start-expression=<START_CRON>] [--stop-expression=<STOP_CRON>] [--state=<enable/disable>]"
133
+ exit
134
+ elsif options[:start_cron].nil? && options[:stop_cron].nil? && options[:state].nil?
135
+ puts "Please provide atleast one attribute to update."
136
+ puts "Usage: hibernate rule --update --rule-name=<RULE_NAME> [--start-expression=<START_CRON>] [--stop-expression=<STOP_CRON>] [--state=<enable/disable>]"
82
137
  exit
83
138
  else
84
- ec2_manager = EC2Manager.new(options[:instance_name], options[:start_cron], options[:stop_cron])
85
- ec2_manager.remove_event_rule
139
+ ec2_manager = EC2Manager.new(options[:instance_name])
140
+ ec2_manager.update_event_rule(
141
+ rule_name: options[:rule_name],
142
+ start_cron: options[:start_cron],
143
+ stop_cron: options[:stop_cron],
144
+ state: options[:state]
145
+ )
86
146
  end
87
147
  end
88
148
  end
@@ -1,9 +1,10 @@
1
1
  require 'aws-sdk-cloudwatchevents'
2
2
  require 'json'
3
3
  require 'dotenv/load'
4
+ require 'digest'
4
5
 
5
6
  class CloudWatchEventManager
6
- def initialize(events_client, instance_id, instance_name, lambda_function_arn)
7
+ def initialize(events_client, instance_id = nil, instance_name = nil, lambda_function_arn)
7
8
  @events_client = events_client
8
9
  @instance_id = instance_id
9
10
  @instance_name = instance_name
@@ -13,38 +14,110 @@ class CloudWatchEventManager
13
14
  @lambda_client = Aws::Lambda::Client.new(region: @aws_region)
14
15
  end
15
16
 
17
+ attr_writer :instance_id, :instance_name
18
+
16
19
  def create_start_rule(cron_expression)
20
+ rule_name = "StartInstanceRule-#{@instance_id}-#{cron_expression_hash(cron_expression)}"
17
21
  create_rule(
18
- "StartInstanceRule-#{@instance_id}",
22
+ rule_name,
19
23
  cron_expression,
20
24
  { instance_id: @instance_id, action: 'start' },
21
- "start"
25
+ 'start'
22
26
  )
23
27
  end
24
28
 
25
29
  def create_stop_rule(cron_expression)
30
+ rule_name = "StopInstanceRule-#{@instance_id}-#{cron_expression_hash(cron_expression)}"
26
31
  create_rule(
27
- "StopInstanceRule-#{@instance_id}",
32
+ rule_name,
28
33
  cron_expression,
29
34
  { instance_id: @instance_id, action: 'stop' },
30
- "stop"
35
+ 'stop'
31
36
  )
32
37
  end
33
38
 
34
- def remove_start_rule
35
- rule_name = "StartInstanceRule-#{@instance_id}"
36
- remove_rule(rule_name)
39
+ def remove_rule(rule_name)
40
+ remove_rule_by_name(rule_name)
37
41
  remove_lambda_permission(rule_name)
38
42
  end
39
43
 
40
- def remove_stop_rule
41
- rule_name = "StopInstanceRule-#{@instance_id}"
42
- remove_rule(rule_name)
43
- remove_lambda_permission(rule_name)
44
+ def list_event_rules(options)
45
+ next_token = nil
46
+
47
+ column_widths = {
48
+ rule_name: 40,
49
+ instance_id: 22,
50
+ schedule: 30,
51
+ state: 10,
52
+ action: 10
53
+ }
54
+
55
+ print_header(column_widths)
56
+
57
+ loop do
58
+ response = @events_client.list_rules(next_token: next_token)
59
+
60
+ response.rules.each do |rule|
61
+ process_rule(rule, options, column_widths)
62
+ end
63
+
64
+ next_token = response.next_token
65
+ break if next_token.nil?
66
+ end
67
+
68
+ print_footer(column_widths)
69
+ end
70
+
71
+ def update_rule_state(rule_name, state)
72
+ state = state == 'enable' ? 'ENABLED' : 'DISABLED'
73
+
74
+ rule_details = @events_client.describe_rule({ name: rule_name })
75
+
76
+ params = {
77
+ name: rule_name,
78
+ state: state,
79
+ description: rule_details.description
80
+ }
81
+
82
+ if rule_details.schedule_expression
83
+ params[:schedule_expression] = rule_details.schedule_expression
84
+ elsif rule_details.event_pattern
85
+ params[:event_pattern] = rule_details.event_pattern
86
+ else
87
+ puts "No ScheduleExpression or EventPattern found for rule '#{rule_name}'."
88
+ exit 1
89
+ end
90
+
91
+ @events_client.put_rule(params)
92
+ end
93
+
94
+ def get_instance_id_from_rule(rule_name)
95
+ response = @events_client.list_targets_by_rule({ rule: rule_name })
96
+ return nil if response.targets.empty?
97
+
98
+ target_input = response.targets[0].input
99
+ parsed_input = JSON.parse(target_input)
100
+ parsed_input['instance_id'] if parsed_input.key?('instance_id')
101
+ rescue Aws::CloudWatchEvents::Errors::ResourceNotFoundException => e
102
+ puts "Error fetching targets for rule: #{rule_name} - #{e.message}"
103
+ nil
104
+ end
105
+
106
+ def rule_exists?(rule_name)
107
+ begin
108
+ response = @events_client.describe_rule({ name: rule_name })
109
+ return true unless response.nil?
110
+ rescue Aws::CloudWatchEvents::Errors::ResourceNotFoundException
111
+ return false
112
+ end
44
113
  end
45
114
 
46
115
  private
47
116
 
117
+ def cron_expression_hash(cron_expression)
118
+ Digest::SHA256.hexdigest(cron_expression)[0..7]
119
+ end
120
+
48
121
  def create_rule(rule_name, cron_expression, input_data, action)
49
122
  @events_client.put_rule({
50
123
  name: rule_name,
@@ -85,7 +158,7 @@ class CloudWatchEventManager
85
158
  end
86
159
  end
87
160
 
88
- def remove_rule(rule_name)
161
+ def remove_rule_by_name(rule_name)
89
162
  @events_client.remove_targets({
90
163
  rule: rule_name,
91
164
  ids: ['1']
@@ -94,7 +167,7 @@ class CloudWatchEventManager
94
167
  name: rule_name
95
168
  })
96
169
 
97
- puts "Removed rule '#{rule_name}' for instance '#{@instance_name}' (ID: #{@instance_id})."
170
+ puts "Removed rule '#{rule_name}'"
98
171
  end
99
172
 
100
173
  def remove_lambda_permission(rule_name)
@@ -108,4 +181,52 @@ class CloudWatchEventManager
108
181
  puts "Permission not found: #{e.message}"
109
182
  end
110
183
  end
184
+
185
+ def print_header(column_widths)
186
+ total_width = column_widths.values.sum + 8
187
+ puts "-" * total_width
188
+ puts "| #{'Rule Name'.ljust(column_widths[:rule_name])} | " \
189
+ "#{ 'Instance ID'.ljust(column_widths[:instance_id])} | " \
190
+ "#{ 'Schedule (UTC)'.ljust(column_widths[:schedule])} | " \
191
+ "#{ 'State'.ljust(column_widths[:state])} | " \
192
+ "#{ 'Action'.ljust(column_widths[:action])} |"
193
+ puts "-" * total_width
194
+ end
195
+
196
+ def print_footer(column_widths)
197
+ total_width = column_widths.values.sum + 8
198
+ puts '-' * total_width
199
+ end
200
+
201
+ def process_rule(rule, options, column_widths)
202
+ targets = @events_client.list_targets_by_rule(rule: rule.name).targets
203
+ target = targets.find { |t| t.arn == @lambda_function_arn }
204
+
205
+ return unless target
206
+
207
+ input = JSON.parse(target.input)
208
+ action = input['action']
209
+ rule_instance_id = input['instance_id']
210
+
211
+ if matches_criteria?(rule_instance_id, action, options)
212
+ print_rule(rule, rule_instance_id, action, column_widths)
213
+ end
214
+ end
215
+
216
+ def matches_criteria?(rule_instance_id, action, options)
217
+ instance_id_match = options[:instance_id].nil? || options[:instance_id] == rule_instance_id
218
+ action_match = (options[:start_instance] && action == 'start') ||
219
+ (options[:stop_instance] && action == 'stop') ||
220
+ (options[:start_instance].nil? && options[:stop_instance].nil?)
221
+
222
+ instance_id_match && action_match
223
+ end
224
+
225
+ def print_rule(rule, rule_instance_id, action, column_widths)
226
+ puts "| #{rule.name.ljust(column_widths[:rule_name])} | " \
227
+ "#{rule_instance_id.ljust(column_widths[:instance_id])} | " \
228
+ "#{rule.schedule_expression.ljust(column_widths[:schedule])} | " \
229
+ "#{rule.state.ljust(column_widths[:state])} | " \
230
+ "#{action.capitalize.ljust(column_widths[:action])} |"
231
+ end
111
232
  end
@@ -4,10 +4,8 @@ require 'json'
4
4
  require_relative 'cloud_watch_event_manager' # Adjust the path to where the new class is located
5
5
 
6
6
  class EC2Manager
7
- def initialize(instance_name, start_cron, stop_cron)
7
+ def initialize(instance_name = nil)
8
8
  @instance_name = instance_name
9
- @start_cron = start_cron
10
- @stop_cron = stop_cron
11
9
  @aws_region = ENV['AWS_REGION']
12
10
  @account_id = ENV['ACCOUNT_ID']
13
11
 
@@ -16,11 +14,13 @@ class EC2Manager
16
14
 
17
15
  @lambda_function_name = "ec2_auto_shutdown_start_function"
18
16
  @lambda_function_arn = construct_lambda_function_arn
19
- @instance_id = get_instance_id_by_name
17
+ @instance_id = get_instance_id_by_name unless @instance_name.nil?
20
18
 
21
19
  @cloudwatch_event_manager = CloudWatchEventManager.new(@events_client, @instance_id, @instance_name, @lambda_function_arn)
22
20
  end
23
21
 
22
+ attr_writer :instance_name, :instance_id
23
+
24
24
  def get_instance_id_by_name
25
25
  response = @ec2_client.describe_instances({
26
26
  filters: [
@@ -39,16 +39,58 @@ class EC2Manager
39
39
  instance_id
40
40
  end
41
41
 
42
- def create_event_rule
43
- @cloudwatch_event_manager.create_start_rule(@start_cron) unless @start_cron.nil?
44
- @cloudwatch_event_manager.create_stop_rule(@stop_cron) unless @stop_cron.nil?
42
+ def create_event_rule(start_cron, stop_cron)
43
+ @cloudwatch_event_manager.create_start_rule(start_cron) unless start_cron.nil?
44
+ @cloudwatch_event_manager.create_stop_rule(stop_cron) unless stop_cron.nil?
45
45
  puts "CloudWatch Events created for instance '#{@instance_name}' (ID: #{@instance_id})."
46
46
  end
47
47
 
48
- def remove_event_rule
49
- @cloudwatch_event_manager.remove_start_rule unless @start_cron.nil?
50
- @cloudwatch_event_manager.remove_stop_rule unless @stop_cron.nil?
51
- puts "CloudWatch Events removed for instance '#{@instance_name}' (ID: #{@instance_id})."
48
+ def remove_event_rule(rule_name)
49
+ @cloudwatch_event_manager.remove_rule(rule_name)
50
+ puts "CloudWatch Events removed for rule: #{rule_name}."
51
+ end
52
+
53
+ def list_event_rules(options)
54
+ options[:instance_id] = @instance_id unless @instance_id.nil?
55
+ @cloudwatch_event_manager.list_event_rules(options)
56
+ end
57
+
58
+ def update_event_rule(rule_name:, start_cron:, stop_cron:, state:)
59
+ rule_exists = @cloudwatch_event_manager.rule_exists?(rule_name)
60
+
61
+ unless rule_exists
62
+ puts "Rule '#{rule_name}' does not exist."
63
+ exit 1
64
+ end
65
+
66
+ target_instance_id = @cloudwatch_event_manager.get_instance_id_from_rule(rule_name)
67
+ if target_instance_id.nil?
68
+ puts "No targets found for the rule '#{rule_name}'."
69
+ exit 1
70
+ end
71
+
72
+ instance_name = get_instance_name_by_id(target_instance_id)
73
+
74
+ @cloudwatch_event_manager.instance_id = target_instance_id
75
+ @cloudwatch_event_manager.instance_name = instance_name
76
+
77
+ self.instance_id = target_instance_id
78
+ self.instance_name = instance_name
79
+
80
+ puts "Found instance ID: #{@instance_id} from the rule: #{rule_name}"
81
+
82
+ if start_cron || stop_cron
83
+ puts "Removing old rule: #{rule_name} as cron expression is being updated."
84
+ remove_event_rule(rule_name)
85
+
86
+ create_event_rule(start_cron, stop_cron)
87
+ puts "Created new rule with updated cron expression for instance '#{@instance_name}' (ID: #{@instance_id})."
88
+ end
89
+
90
+ return if state.nil?
91
+
92
+ @cloudwatch_event_manager.update_rule_state(rule_name, state)
93
+ puts "Rule '#{rule_name}' has been #{state == 'enable' ? 'enabled' : 'disabled'}."
52
94
  end
53
95
 
54
96
  private
@@ -56,4 +98,21 @@ class EC2Manager
56
98
  def construct_lambda_function_arn
57
99
  "arn:aws:lambda:#{@aws_region}:#{@account_id}:function:#{@lambda_function_name}"
58
100
  end
101
+
102
+ def get_instance_name_by_id(instance_id)
103
+ response = @ec2_client.describe_instances({
104
+ instance_ids: [instance_id]
105
+ })
106
+
107
+ if response.reservations.empty?
108
+ puts "No instance found with ID '#{instance_id}'."
109
+ return nil
110
+ end
111
+
112
+ instance = response.reservations[0].instances[0]
113
+ tags = instance.tags || []
114
+
115
+ name_tag = tags.find { |tag| tag.key == 'Name' }
116
+ name_tag&.value
117
+ end
59
118
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hibernate
2
- VERSION = "0.1.1"
3
- end
4
+ VERSION = '0.1.3'
5
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hibernate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Manish Sharma
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-09-30 00:00:00.000000000 Z
11
+ date: 2024-10-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk-ec2