|
| 1 | +#!/bin/env ruby |
| 2 | + |
| 3 | +require 'aws-sdk-ecs' |
| 4 | +require 'aws-sdk-cloudwatchlogs' |
| 5 | +require 'optparse' |
| 6 | +require 'shellwords' |
| 7 | + |
| 8 | +config = {} |
| 9 | +OptionParser.new do |opts| |
| 10 | + opts.banner = 'Usage: ecs_run.rb [options] [command or STDIN]' |
| 11 | + |
| 12 | + opts.on('-c', '--cluster=CLUSTER', 'Cluster name') do |c| |
| 13 | + config[:cluster] = c |
| 14 | + end |
| 15 | + |
| 16 | + opts.on('-s', '--service=SERVICE', 'Service name') do |s| |
| 17 | + config[:service] = s |
| 18 | + end |
| 19 | + |
| 20 | + opts.on('-w', '--watch', 'Watch output') do |s| |
| 21 | + config[:watch] = true |
| 22 | + end |
| 23 | + |
| 24 | + opts.on('-r', '--ruby', 'Run input as Ruby code with Rails runner (instead of shell command)') do |r| |
| 25 | + config[:ruby] = true |
| 26 | + end |
| 27 | +end.parse! |
| 28 | +raise OptionParser::MissingArgument, 'cluster' if config[:cluster].nil? |
| 29 | +raise OptionParser::MissingArgument, 'service' if config[:service].nil? |
| 30 | + |
| 31 | +command = ARGV[0] |
| 32 | +unless command |
| 33 | + puts 'Type your command then press Ctrl+D' if STDIN.tty? |
| 34 | + puts 'Note - Ruby evaluation result is NOT automatically printed, use `p`' if config[:ruby] |
| 35 | + puts |
| 36 | + command = STDIN.read |
| 37 | + puts |
| 38 | +end |
| 39 | +command = "bundle exec rails runner #{command.shellescape}" if config[:ruby] |
| 40 | + |
| 41 | +client = Aws::ECS::Client.new |
| 42 | + |
| 43 | +resp = client.describe_services( |
| 44 | + cluster: config[:cluster], |
| 45 | + services: [ |
| 46 | + config[:service] |
| 47 | + ] |
| 48 | +) |
| 49 | +service = resp.services[0] |
| 50 | + |
| 51 | +task_definition = client.describe_task_definition(task_definition: service.task_definition).task_definition |
| 52 | + |
| 53 | +if task_definition.container_definitions.length > 1 |
| 54 | + raise 'Running in tasks with more than one container is not yet supported' |
| 55 | +end |
| 56 | + |
| 57 | +container_name = task_definition.container_definitions.first.name |
| 58 | + |
| 59 | +vpc_config = service.deployments[0].network_configuration.awsvpc_configuration |
| 60 | + |
| 61 | +subnet = vpc_config.subnets[0] |
| 62 | +security_group = vpc_config.security_groups[0] |
| 63 | + |
| 64 | +task_response = client.run_task( |
| 65 | + cluster: config[:cluster], |
| 66 | + task_definition: service.task_definition, |
| 67 | + launch_type: 'FARGATE', |
| 68 | + overrides: { |
| 69 | + container_overrides: [ |
| 70 | + { |
| 71 | + name: container_name, |
| 72 | + command: ['sh', '-c', command] |
| 73 | + } |
| 74 | + ] |
| 75 | + }, |
| 76 | + network_configuration: { |
| 77 | + awsvpc_configuration: { |
| 78 | + subnets: [subnet], |
| 79 | + security_groups: [security_group] |
| 80 | + } |
| 81 | + } |
| 82 | +) |
| 83 | + |
| 84 | +task_arn = task_response.tasks[0].task_arn |
| 85 | +task_arn_parts = task_arn.split(':') |
| 86 | +task_region = task_arn_parts[3] |
| 87 | +task_id = task_arn_parts[5].split('/').last |
| 88 | + |
| 89 | +puts "Task started. See it online at https://#{task_region}.console.aws.amazon.com/ecs/home?region=#{task_region}#/clusters/#{config[:cluster]}/tasks/#{task_id}/details" |
| 90 | + |
| 91 | +exit unless config[:watch] |
| 92 | + |
| 93 | +puts 'Watching task. Note - Ctrl+C will stop watching, but will NOT stop the task!' |
| 94 | +last_notified_status = '' |
| 95 | + |
| 96 | +log_configuration = task_definition.container_definitions.first.log_configuration |
| 97 | +log_client = nil |
| 98 | +log_stream_name = nil |
| 99 | +log_token = nil |
| 100 | +if log_configuration.log_driver == 'awslogs' |
| 101 | + log_client = Aws::CloudWatchLogs::Client.new |
| 102 | + log_stream_name = "#{log_configuration.options['awslogs-stream-prefix']}/#{container_name}/#{task_id}" |
| 103 | + log_token = nil |
| 104 | +else |
| 105 | + puts 'Use `awslogs` log adapter to see the task output.' |
| 106 | +end |
| 107 | + |
| 108 | +loop do |
| 109 | + task_status = client.describe_tasks(cluster: config[:cluster], tasks: [task_id]).tasks[0].last_status |
| 110 | + if task_status != last_notified_status |
| 111 | + puts "[#{Time.now}] Task status changed to #{task_status}" |
| 112 | + last_notified_status = task_status |
| 113 | + break if task_status == 'STOPPED' |
| 114 | + end |
| 115 | + |
| 116 | + if log_client && %w[RUNNING DEPROVISIONING].include?(task_status) |
| 117 | + events_resp = log_client.get_log_events( |
| 118 | + log_group_name: log_configuration.options['awslogs-group'], |
| 119 | + log_stream_name: log_stream_name, |
| 120 | + start_from_head: true, |
| 121 | + next_token: log_token |
| 122 | + ) |
| 123 | + events_resp.events.each do |event| |
| 124 | + puts "[#{Time.at(event.timestamp / 1000)}] #{event.message}" |
| 125 | + end |
| 126 | + log_token = events_resp.next_forward_token |
| 127 | + end |
| 128 | + sleep 1 |
| 129 | +end |
0 commit comments