Class: Putpaws::Ecs::RunCommand

Inherits:
Object
  • Object
show all
Defined in:
lib/putpaws/ecs/run_command.rb

Constant Summary collapse

DEFAULT_TTL =
'30m'
SECONDS =
{
  's' => 1,
  'm' => 60,
  'h' => 60 * 60,
}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(region:, network:, target:) ⇒ RunCommand

Returns a new instance of RunCommand.



35
36
37
38
39
40
# File 'lib/putpaws/ecs/run_command.rb', line 35

def initialize(region:, network:, target:)
  @ecs_client = Aws::ECS::Client.new({region: region})
  @region = region
  @network = network
  @target = target
end

Instance Attribute Details

#ecs_clientObject (readonly)

Returns the value of attribute ecs_client.



33
34
35
# File 'lib/putpaws/ecs/run_command.rb', line 33

def ecs_client
  @ecs_client
end

#networkObject (readonly)

Returns the value of attribute network.



34
35
36
# File 'lib/putpaws/ecs/run_command.rb', line 34

def network
  @network
end

#regionObject (readonly)

Returns the value of attribute region.



34
35
36
# File 'lib/putpaws/ecs/run_command.rb', line 34

def region
  @region
end

#targetObject (readonly)

Returns the value of attribute target.



34
35
36
# File 'lib/putpaws/ecs/run_command.rb', line 34

def target
  @target
end

Class Method Details

.config(config) ⇒ Object



12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/putpaws/ecs/run_command.rb', line 12

def self.config(config)
  unless config.network && config.target
    raise "Please set `network` and `target` at .putpaws/application.json\n" +
      "They refer to `network` and `target` sections at .putpaws/infra.json\n" +
      "(Ex) \"network\": \"awesome-private-staging\", \"target\": \"awesome-staging\""
  end
  new(
    region: config.ecs_region || config.region,
    network: config.network,
    target: config.target,
  )
end

.parse_ttl(ut) ⇒ Object

"45" and "45m" mean 45 minutes. "90s" and "2h" are also acceptable.



26
27
28
29
30
31
# File 'lib/putpaws/ecs/run_command.rb', line 26

def self.parse_ttl(ut)
  ut = DEFAULT_TTL if ut.nil? || ut.strip.empty?
  matched, number, unit = ut.strip.match(/\A(\d+)([smh])?\z/).to_a
  raise "Invalid ttl: #{ut} (Ex) ttl=30m ttl=2h ttl=90s" unless matched
  number.to_i * SECONDS[unit || 'm']
end

Instance Method Details

#cluster_nameObject



42
43
44
# File 'lib/putpaws/ecs/run_command.rb', line 42

def cluster_name
  target.cluster.split('/').last
end

#container_nameObject



46
47
48
# File 'lib/putpaws/ecs/run_command.rb', line 46

def container_name
  target.container_name || 'app'
end

#run_ecs_task(command:, started_by:, group:, container: nil) ⇒ Object

Launch a temporary task whose main process is overridden with the given command, so that the task terminates itself when the command finishes. started_by and group are set so that temporary tasks are identifiable.



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/putpaws/ecs/run_command.rb', line 53

def run_ecs_task(command:, started_by:, group:, container: nil)
  res = ecs_client.run_task({
    cluster: target.cluster,
    task_definition: target.task_definition,
    count: 1,
    launch_type: 'FARGATE',
    enable_execute_command: true,
    started_by: started_by,
    group: group,
    network_configuration: {
      awsvpc_configuration: {
        subnets: network.subnets,
        security_groups: network.security_groups,
        assign_public_ip: network.assign_public_ip,
      }
    },
    overrides: {
      container_overrides: [
        {
          name: container || container_name,
          command: ['/bin/sh', '-c', command],
        }
      ]
    },
  })
  failure = res.failures.first
  raise "Failed to run task: #{failure.reason} #{failure.detail}" if failure
  res.tasks.first
end

#stop_ecs_task(task_arn, reason: 'Stopped by putpaws') ⇒ Object



111
112
113
# File 'lib/putpaws/ecs/run_command.rb', line 111

def stop_ecs_task(task_arn, reason: 'Stopped by putpaws')
  ecs_client.stop_task(cluster: target.cluster, task: task_arn, reason: reason)
end

#wait_until_attachable(task_arn, timeout: 600, interval: 5) ⇒ Object



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/putpaws/ecs/run_command.rb', line 83

def wait_until_attachable(task_arn, timeout: 600, interval: 5)
  deadline = Time.now + timeout
  loop do
    res = ecs_client.describe_tasks(cluster: target.cluster, tasks: [task_arn])
    task = res.tasks.first
    if task.nil? || task.last_status == 'STOPPED'
      raise "Task was stopped: #{task && task.stopped_reason}"
    end
    ctn = task.containers.detect{|c| c.name == container_name}
    agent = ctn && (ctn.managed_agents || []).detect{|a| a.name == 'ExecuteCommandAgent'}
    return task if task.last_status == 'RUNNING' && agent && agent.last_status == 'RUNNING'
    raise "Timed out waiting for task to be attachable: #{task_arn}" if Time.now > deadline
    sleep interval
  end
end

#wait_until_stopped(task_arn, timeout: 3600, interval: 10) ⇒ Object



99
100
101
102
103
104
105
106
107
108
109
# File 'lib/putpaws/ecs/run_command.rb', line 99

def wait_until_stopped(task_arn, timeout: 3600, interval: 10)
  deadline = Time.now + timeout
  loop do
    res = ecs_client.describe_tasks(cluster: target.cluster, tasks: [task_arn])
    task = res.tasks.first
    raise "Task not found: #{task_arn}" unless task
    return task if task.last_status == 'STOPPED'
    raise "Timed out waiting for task to stop: #{task_arn}" if Time.now > deadline
    sleep interval
  end
end