ecs-rails 0.0.5 → 0.0.7

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: dfe4933e3eb4620330076f2125af1d6cb0d618f06f52ebf7594e09bd96ee814d
4
- data.tar.gz: 504edec780c9ac72495e47a62a9ba947a2cb5f5c54efe0168d4c97ae3983eb59
3
+ metadata.gz: 17f65165aa5cdd39910dbbd8c5fe33fa9efdbb35d84d997eeb2a614ee60d799e
4
+ data.tar.gz: f1e47bc8be3542a5bf9373772fc4e9c58bff9b407c01096dd5a428d8f4f151b5
5
5
  SHA512:
6
- metadata.gz: 468eeaa07559226e3cb18e7445d4fe087823b5152efb1b2b436d12b21d351728931cfeba4ee90118a090c611495e2cdefce799f77b6c378224702c0913d6fcc8
7
- data.tar.gz: 1830eaff6a0e8852e2b563efb5cace637eed17a5169fd63de2c960c5cbfa9bf9e7d484e75b9c96f581da091d2b037eda60db702e27de35d5fd4102778daec5db
6
+ metadata.gz: c89cb54ab55232008edd3f87065e9b343c3a0a145af97b828e4139d68b2f92fe799792f04d73854c23457c312f60d4c4072c7b47a05e98fcca1bb773866ea2d0
7
+ data.tar.gz: 85a65061e5a6cce28457471579e09bf3e4aabb47ef13f05130bb7d51f703982874a0e4e96555af038075595b72139b38513920635cbd3428f187462bd66b7a2c
data/README.md CHANGED
@@ -9,6 +9,8 @@ This gem helps to get the correct cluster arn and task id so that you don't have
9
9
  ```bash
10
10
  ecs console
11
11
  ecs bash
12
+ ecs logs
13
+ ecs scale
12
14
  ```
13
15
 
14
16
  # Installation
@@ -25,7 +27,7 @@ gem install 'ecs-rails'
25
27
 
26
28
  # Configuration
27
29
 
28
- ## Plain Ruby
30
+ ## environment variables
29
31
 
30
32
  Via environment variables:
31
33
 
@@ -36,18 +38,16 @@ export ENV['AWS_SECRET_ACCESS_KEY'] = 'your-secret-access-key'
36
38
  export ENV['CONTAINER_NAME'] = 'your-container-name'
37
39
  ```
38
40
 
39
- ## Rails
41
+ ## AWS SSO
40
42
 
41
- ```ruby
42
- # config/initializers/ecs-rails.rb
43
- EcsRails.aws_region = 'us-east-1'
44
- EcsRails.aws_access_key_id = ENV['AWS_ACCESS_KEY_ID']
45
- EcsRails.aws_secret_access_key = ENV['AWS_SECRET_ACCESS_KEY']
46
- EcsRails.container_name = 'webapp'
43
+ ```
44
+ aws sso login
47
45
  ```
48
46
 
49
47
  # Usage
50
48
 
49
+ ## Console
50
+
51
51
  Connect to Rails console on a running Task.
52
52
 
53
53
  ```ruby
@@ -75,3 +75,67 @@ You can specify service name via -s option by giving a string included in servic
75
75
  ecs console -c prod -s app
76
76
  irb(main)>
77
77
  ```
78
+
79
+ ## Bash
80
+
81
+ Connect to bash session on a running Task.
82
+
83
+ ```bash
84
+ ecs bash -c prod -s app
85
+ bash-4.2#
86
+ ```
87
+
88
+ ## Logs
89
+
90
+ Tail CloudWatch logs for a service.
91
+
92
+ ```bash
93
+ ecs logs -c prod -s app
94
+
95
+ Fetching log group from task definition...
96
+ Log group: webapp-app-log-group-prod-827a419
97
+ Executing command: aws logs tail webapp-app-log-group-prod-827a419 --region us-east-1 --follow --format short
98
+ ```
99
+
100
+ The logs command will:
101
+ - Automatically discover the CloudWatch log group from your ECS task definition
102
+ - Tail logs in real-time with `--follow` flag
103
+ - Display logs in a short, readable format
104
+ - Press Ctrl-C to exit
105
+
106
+ ## Scale
107
+
108
+ View the current scaling status of a service (refreshes every 5 seconds):
109
+
110
+ ```bash
111
+ ecs scale -c prod -s app
112
+
113
+ Press Ctrl-C to exit
114
+
115
+ Fetching service status... (refreshes every 5 seconds)
116
+
117
+ Service: webapp-app-prod-7c7cad7
118
+ Cluster: webapp-cluster-prod
119
+ Status: ACTIVE
120
+
121
+ Task counts:
122
+ Desired: 2
123
+ Running: 2
124
+ Pending: 0
125
+ ```
126
+
127
+ Scale a service up or down by changing the desired task count:
128
+
129
+ ```bash
130
+ ecs scale -c prod -s app --count 5
131
+
132
+ Fetching service status...
133
+ Current desired count: 2
134
+ Scaling service 'webapp-app-prod-7c7cad7' to 5 tasks...
135
+ ✓ Service scaled successfully!
136
+ New desired count: 5
137
+ ```
138
+
139
+ The scale command will:
140
+ - Without `--count`: Show the current status including desired, running, and pending task counts, refreshing every 5 seconds. Press Ctrl-C to exit.
141
+ - With `--count`: Update the service's desired count to the specified number and confirm the operation
data/bin/ecs CHANGED
@@ -21,6 +21,10 @@ OptionParser.new do |opts|
21
21
  options[:config_file] = c
22
22
  end
23
23
 
24
+ opts.on("--count COUNT", "Number of tasks to scale to. Ex: 'ecs scale -c prod -s app --count 3'") do |c|
25
+ options[:count] = c
26
+ end
27
+
24
28
  end.parse!
25
29
 
26
30
  command_keyword = ARGV[0]
@@ -1,10 +1,10 @@
1
1
  module EcsRails
2
2
  class CommandFactory
3
-
4
3
  def initialize(command_keyword, options = {})
5
4
  @command_keyword = command_keyword
6
5
  @cluster_name = options[:cluster]
7
6
  @service_name = options[:service]
7
+ @count = options[:count]
8
8
  end
9
9
 
10
10
  def command
@@ -13,6 +13,10 @@ module EcsRails
13
13
  EcsRails::Console.new(cluster_name, service_name)
14
14
  when 'bash'
15
15
  EcsRails::Bash.new(cluster_name, service_name)
16
+ when 'logs'
17
+ EcsRails::Logs.new(cluster_name, service_name)
18
+ when 'scale'
19
+ EcsRails::Scale.new(cluster_name, service_name, count)
16
20
  else
17
21
  EcsRails::NullCommand.new
18
22
  end
@@ -20,7 +24,6 @@ module EcsRails
20
24
 
21
25
  private
22
26
 
23
- attr_reader :command_keyword, :cluster_name, :service_name
24
-
27
+ attr_reader :command_keyword, :cluster_name, :service_name, :count
25
28
  end
26
29
  end
@@ -0,0 +1,43 @@
1
+ module EcsRails
2
+ class Logs < Command
3
+ def call
4
+ return full_command if test_mode?
5
+
6
+ puts "Executing command: #{full_command}"
7
+ system(full_command)
8
+ end
9
+
10
+ private
11
+
12
+ def full_command
13
+ @full_command ||= "aws logs tail #{log_group_name} --region #{region} --follow --format short"
14
+ end
15
+
16
+ def log_group_name
17
+ puts('Fetching log group from task definition...')
18
+
19
+ service_descriptions = client.describe_services(
20
+ cluster: selected_cluster,
21
+ services: [selected_service]
22
+ ).services
23
+
24
+ task_definition_arn = service_descriptions.first.task_definition
25
+
26
+ task_definition = client.describe_task_definition(
27
+ task_definition: task_definition_arn
28
+ ).task_definition
29
+
30
+ log_config = task_definition.container_definitions.find { |c| c.name == container_name }&.log_configuration
31
+
32
+ if log_config && log_config.log_driver == 'awslogs'
33
+ group_name = log_config.options['awslogs-group']
34
+ puts("Log group: #{group_name}")
35
+ group_name
36
+ else
37
+ puts('Warning: Could not find awslogs configuration, trying default /ecs/[service-name] format')
38
+ service_name_from_arn = selected_service.split('/').last
39
+ "/ecs/#{service_name_from_arn}"
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,94 @@
1
+ module EcsRails
2
+ class Scale < Command
3
+ def initialize(cluster_name = nil, service_name = nil, count = nil)
4
+ super(cluster_name, service_name)
5
+ @count = count
6
+ end
7
+
8
+ def call
9
+ if count.nil?
10
+ show_status_loop
11
+ else
12
+ scale_service_once
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ attr_reader :count
19
+
20
+ def show_status_loop
21
+ puts "Press Ctrl-C to exit\n\n"
22
+
23
+ trap('INT') do
24
+ puts "\nExiting..."
25
+ exit 0
26
+ end
27
+
28
+ loop do
29
+ print "\033[2J\033[H"
30
+
31
+ puts "Fetching service status... (refreshes every 5 seconds)\n"
32
+
33
+ service_descriptions = client.describe_services(
34
+ cluster: selected_cluster,
35
+ services: [selected_service]
36
+ ).services
37
+
38
+ service = service_descriptions.first
39
+ service_name = service.service_name
40
+
41
+ display_status(service, service_name)
42
+
43
+ sleep 5
44
+ end
45
+ end
46
+
47
+ def scale_service_once
48
+ puts 'Fetching service status...'
49
+
50
+ service_descriptions = client.describe_services(
51
+ cluster: selected_cluster,
52
+ services: [selected_service]
53
+ ).services
54
+
55
+ service = service_descriptions.first
56
+ service_name = service.service_name
57
+ current_count = service.desired_count
58
+
59
+ puts "Current desired count: #{current_count}"
60
+ puts "Scaling service '#{service_name}' to #{count} tasks..."
61
+
62
+ response = client.update_service(
63
+ cluster: selected_cluster,
64
+ service: selected_service,
65
+ desired_count: count.to_i
66
+ )
67
+
68
+ new_count = response.service.desired_count
69
+ puts '✓ Service scaled successfully!'
70
+ puts "New desired count: #{new_count}"
71
+ end
72
+
73
+ def display_status(service, service_name)
74
+ puts "\nService: #{service_name}"
75
+ puts "Cluster: #{service.cluster_arn.split('/').last}"
76
+ puts "Status: #{service.status}"
77
+ puts "\nTask counts:"
78
+ puts " Desired: #{service.desired_count}"
79
+ puts " Running: #{service.running_count}"
80
+ puts " Pending: #{service.pending_count}"
81
+
82
+ return unless service.deployments.length > 1
83
+
84
+ puts "\nDeployments:"
85
+ service.deployments.each_with_index do |deployment, index|
86
+ status_label = deployment.rollout_state == 'COMPLETED' ? '(primary)' : '(in progress)'
87
+ puts " #{index + 1}. #{status_label}"
88
+ puts " Desired: #{deployment.desired_count}"
89
+ puts " Running: #{deployment.running_count}"
90
+ puts " Pending: #{deployment.pending_count}"
91
+ end
92
+ end
93
+ end
94
+ end
@@ -1,6 +1,5 @@
1
1
  module EcsRails
2
2
  class TaskSelector
3
-
4
3
  def initialize(client, cluster_name, service_name)
5
4
  @client = client
6
5
  @cluster_name = cluster_name
@@ -13,7 +12,8 @@ module EcsRails
13
12
 
14
13
  service_descriptions.each do |service|
15
14
  service.deployments.each do |deployment|
16
- task_arns = client.list_tasks(cluster: cluster_name, service_name: service.service_name, desired_status: 'RUNNING').task_arns
15
+ task_arns = client.list_tasks(cluster: cluster_name, service_name: service.service_name,
16
+ desired_status: 'RUNNING').task_arns
17
17
  next if task_arns.empty?
18
18
 
19
19
  tasks = client.describe_tasks(cluster: cluster_name, tasks: task_arns).tasks
@@ -22,12 +22,12 @@ module EcsRails
22
22
  tasks_for_deployment = tasks.select { |task| task.task_definition_arn == deployment.task_definition }
23
23
 
24
24
  if tasks_for_deployment.empty?
25
- puts(" No tasks found for this deployment.")
25
+ puts(' No tasks found for this deployment.')
26
26
  elsif tasks_for_deployment.size == 1
27
27
  puts(" Task: #{task_arns.first}")
28
- return task_arns.first
28
+ return task_arns.first
29
29
  else
30
- ask_for_task(tasks_for_deployment)
30
+ return ask_for_task(tasks_for_deployment)
31
31
  end
32
32
  end
33
33
  end
@@ -35,13 +35,13 @@ module EcsRails
35
35
 
36
36
  private
37
37
 
38
- attr_reader :client, :cluster_name, :service_name
38
+ attr_reader :client, :cluster_name, :service_name
39
39
 
40
- def ask_for_task(tasks)
41
- prompt = TTY::Prompt.new
42
- choices = tasks.map { |task| task.split('/').last }
43
- choice = prompt.enum_select("Select a task:", choices)
44
- tasks.grep(Regexp.new(choice)).first
45
- end
40
+ def ask_for_task(tasks)
41
+ prompt = TTY::Prompt.new
42
+ choices = tasks.map { |task| task.task_arn.split('/').last }
43
+ choice = prompt.enum_select('Select a task:', choices)
44
+ tasks.find { |task| task.task_arn.include?(choice) }.task_arn
45
+ end
46
46
  end
47
47
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module EcsRails
4
- VERSION = '0.0.5'
4
+ VERSION = '0.0.7'
5
5
  end
data/lib/ecs-rails.rb CHANGED
@@ -22,21 +22,21 @@ require_relative 'ecs-rails/command_factory'
22
22
  require_relative 'ecs-rails/command'
23
23
  require_relative 'ecs-rails/console'
24
24
  require_relative 'ecs-rails/bash'
25
+ require_relative 'ecs-rails/logs'
26
+ require_relative 'ecs-rails/scale'
25
27
  require_relative 'ecs-rails/null_command'
26
28
 
27
29
  # delegate
28
30
  require 'active_support/core_ext/module/delegation'
29
31
 
30
32
  # prompt
31
- require "tty-prompt"
33
+ require 'tty-prompt'
32
34
 
33
35
  module EcsRails
34
-
35
36
  class << self
36
37
  def config
37
38
  EcsRails::EcsRailsConfiguration.instance
38
39
  end
39
40
  delegate(*EcsRails::EcsRailsConfiguration.delegated, to: :config)
40
41
  end
41
-
42
42
  end
@@ -0,0 +1,96 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe EcsRails::TaskSelector do
4
+ let(:client) { instance_double(Aws::ECS::Client) }
5
+ let(:cluster_name) { 'test-cluster' }
6
+ let(:service_name) { 'test-service' }
7
+ let(:selector) { described_class.new(client, cluster_name, service_name) }
8
+
9
+ describe '#call' do
10
+ let(:service) { instance_double(Aws::ECS::Types::Service, service_name: service_name, deployments: [deployment]) }
11
+ let(:deployment) { instance_double(Aws::ECS::Types::Deployment, task_definition: 'task-def-arn') }
12
+ let(:task_arn) { 'arn:aws:ecs:us-east-1:123456789:task/cluster/abc123' }
13
+
14
+ before do
15
+ allow(client).to receive(:describe_services)
16
+ .with(cluster: cluster_name, services: [service_name])
17
+ .and_return(instance_double(Aws::ECS::Types::DescribeServicesResponse, services: [service]))
18
+ end
19
+
20
+ context 'when there are no running tasks' do
21
+ before do
22
+ allow(client).to receive(:list_tasks)
23
+ .with(cluster: cluster_name, service_name: service_name, desired_status: 'RUNNING')
24
+ .and_return(instance_double(Aws::ECS::Types::ListTasksResponse, task_arns: []))
25
+ end
26
+
27
+ it 'does not return a task ARN' do
28
+ result = selector.call
29
+ expect(result).not_to be_a(String)
30
+ end
31
+ end
32
+
33
+ context 'when there is one running task' do
34
+ let(:task) { instance_double(Aws::ECS::Types::Task, task_definition_arn: 'task-def-arn', task_arn: task_arn) }
35
+
36
+ before do
37
+ allow(client).to receive(:list_tasks)
38
+ .with(cluster: cluster_name, service_name: service_name, desired_status: 'RUNNING')
39
+ .and_return(instance_double(Aws::ECS::Types::ListTasksResponse, task_arns: [task_arn]))
40
+
41
+ allow(client).to receive(:describe_tasks)
42
+ .with(cluster: cluster_name, tasks: [task_arn])
43
+ .and_return(instance_double(Aws::ECS::Types::DescribeTasksResponse, tasks: [task]))
44
+ end
45
+
46
+ it 'returns the task ARN' do
47
+ expect(selector.call).to eq(task_arn)
48
+ end
49
+ end
50
+
51
+ context 'when there are multiple running tasks' do
52
+ let(:task_arn_1) { 'arn:aws:ecs:us-east-1:123456789:task/cluster/abc123' }
53
+ let(:task_arn_2) { 'arn:aws:ecs:us-east-1:123456789:task/cluster/def456' }
54
+ let(:task_1) { instance_double(Aws::ECS::Types::Task, task_definition_arn: 'task-def-arn', task_arn: task_arn_1) }
55
+ let(:task_2) { instance_double(Aws::ECS::Types::Task, task_definition_arn: 'task-def-arn', task_arn: task_arn_2) }
56
+ let(:prompt) { instance_double(TTY::Prompt) }
57
+
58
+ before do
59
+ allow(client).to receive(:list_tasks)
60
+ .with(cluster: cluster_name, service_name: service_name, desired_status: 'RUNNING')
61
+ .and_return(instance_double(Aws::ECS::Types::ListTasksResponse, task_arns: [task_arn_1, task_arn_2]))
62
+
63
+ allow(client).to receive(:describe_tasks)
64
+ .with(cluster: cluster_name, tasks: [task_arn_1, task_arn_2])
65
+ .and_return(instance_double(Aws::ECS::Types::DescribeTasksResponse, tasks: [task_1, task_2]))
66
+
67
+ allow(TTY::Prompt).to receive(:new).and_return(prompt)
68
+ allow(prompt).to receive(:enum_select).with('Select a task:', %w[abc123 def456]).and_return('abc123')
69
+ end
70
+
71
+ it 'prompts the user to select a task and returns the selected task ARN' do
72
+ expect(selector.call).to eq(task_arn_1)
73
+ end
74
+ end
75
+
76
+ context 'when tasks do not match the deployment task definition' do
77
+ let(:task_arn) { 'arn:aws:ecs:us-east-1:123456789:task/cluster/abc123' }
78
+ let(:task) { instance_double(Aws::ECS::Types::Task, task_definition_arn: 'different-task-def', task_arn: task_arn) }
79
+
80
+ before do
81
+ allow(client).to receive(:list_tasks)
82
+ .with(cluster: cluster_name, service_name: service_name, desired_status: 'RUNNING')
83
+ .and_return(instance_double(Aws::ECS::Types::ListTasksResponse, task_arns: [task_arn]))
84
+
85
+ allow(client).to receive(:describe_tasks)
86
+ .with(cluster: cluster_name, tasks: [task_arn])
87
+ .and_return(instance_double(Aws::ECS::Types::DescribeTasksResponse, tasks: [task]))
88
+ end
89
+
90
+ it 'does not return a task ARN' do
91
+ result = selector.call
92
+ expect(result).not_to be_a(String)
93
+ end
94
+ end
95
+ end
96
+ end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ecs-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Franck D'agostini
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-05-16 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: byebug
@@ -140,18 +139,20 @@ files:
140
139
  - lib/ecs-rails/command_factory.rb
141
140
  - lib/ecs-rails/console.rb
142
141
  - lib/ecs-rails/ecs_rails_configuration.rb
142
+ - lib/ecs-rails/logs.rb
143
143
  - lib/ecs-rails/null_command.rb
144
+ - lib/ecs-rails/scale.rb
144
145
  - lib/ecs-rails/service_selector.rb
145
146
  - lib/ecs-rails/task_selector.rb
146
147
  - lib/ecs-rails/version.rb
147
148
  - spec/ecs-rails/ecs_rails_configuration_spec.rb
149
+ - spec/ecs-rails/task_selector_spec.rb
148
150
  - spec/fixtures/ecs-rails.yml
149
151
  - spec/spec_helper.rb
150
152
  homepage: https://rubygems.org/gems/ecs-rails
151
153
  licenses:
152
154
  - MIT
153
155
  metadata: {}
154
- post_install_message:
155
156
  rdoc_options: []
156
157
  require_paths:
157
158
  - lib
@@ -166,8 +167,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
166
167
  - !ruby/object:Gem::Version
167
168
  version: '0'
168
169
  requirements: []
169
- rubygems_version: 3.4.12
170
- signing_key:
170
+ rubygems_version: 3.6.9
171
171
  specification_version: 4
172
172
  summary: Connect to your AWS ECS tasks
173
173
  test_files: []