From c2931eb0c5bdd61dca22fedf09c9798416bc0bb1 Mon Sep 17 00:00:00 2001 From: Ryan Schlesinger Date: Tue, 31 Jan 2023 17:26:58 -0400 Subject: [PATCH 1/6] WIP: Added `dev console` It knows how to start a task for 10 seconds and kill it. --- bin/dev | 17 ++++++++-- lib/console.rb | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 lib/console.rb diff --git a/bin/dev b/bin/dev index 3eb6673..5eb6df5 100755 --- a/bin/dev +++ b/bin/dev @@ -12,6 +12,9 @@ gemfile do gem 'tty-command', '~> 0.10.1' gem 'tty-prompt' gem 'pastel' + gem 'tty-option' + gem 'aws-sdk-ecs' + gem 'nokogiri' end require_relative "../lib/state" @@ -36,10 +39,11 @@ Commands: deploy Deploy wrapper for fly setup_network Install systemd networking clean Clean the current project + console Launch a prod console for the current project HEREDOC - TOP_COMMANDS=%w{compose update post_update unset_docker_env mkcert provision_secrets clean deploy setup_network} + TOP_COMMANDS=%w{compose update post_update unset_docker_env mkcert provision_secrets clean deploy setup_network console} CONFIG_DIR = Pathname.new("~/.config/dev").expand_path SHARED_CONTAINERS_DIR = Pathname.new("/opt/shared_containers") @@ -186,8 +190,8 @@ HEREDOC def mkcert(*args) options = {} - OptionParser.new do |opts| - opts.on("--domain=DOMAIN") + OptionParser.new do |parser| + parser.on("--domain=DOMAIN") end.order_recognized!(*args, into: options) %w[mkcert cacerts certs].each do |dir| @@ -383,6 +387,13 @@ HEREDOC setup_network end + def console(*args) + require_relative "../lib/console" + cmd = Console.new + cmd.parse(*args) + cmd.run + end + private def state diff --git a/lib/console.rb b/lib/console.rb new file mode 100644 index 0000000..3a75726 --- /dev/null +++ b/lib/console.rb @@ -0,0 +1,86 @@ +class Console + include TTY::Option + + usage do + program "dev" + end + + option :cluster do + required + desc "ECS cluster name" + long "--cluster string" + end + + option :task_definition do + required + desc "ECS task definition" + long "--task-definition string" + end + + flag :help do + short "-h" + long "--help" + desc "Print usage" + end + + def run + if params[:help] + print help + exit + elsif params.errors.any? + puts params.errors.summary + puts + print help + exit + else + interactive_console + end + end + + private + + def interactive_console + start_task + sleep 10 + stop_task + end + + def start_task + overrides = { + container_overrides: [ + { + name: "certbot-renew", + command: ["sleep", "infinity"] + } + ] + } + + resp = + client.run_task( + cluster: params[:cluster], + task_definition: params[:task_definition], + overrides: overrides, + started_by: "user-console/`whoami`" + ) + tasks = resp.tasks + puts tasks.inspect + + @task_arn = tasks.first.task_arn + end + + def stop_task + resp = + client.stop_task( + cluster: params[:cluster], + task: @task_arn, + reason: "user-console/stop" + ) + end + + def client + return @client if defined?(@client) + + @client = + Aws::ECS::Client.new + end +end From 8f2c300422180435ff9b110359749ea3f59fa4a5 Mon Sep 17 00:00:00 2001 From: Ryan Schlesinger Date: Fri, 3 Feb 2023 11:59:48 -0400 Subject: [PATCH 2/6] Add terrible hack that works... --- lib/console.rb | 97 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 89 insertions(+), 8 deletions(-) diff --git a/lib/console.rb b/lib/console.rb index 3a75726..7238f4c 100644 --- a/lib/console.rb +++ b/lib/console.rb @@ -1,5 +1,8 @@ +require_relative "state" + class Console include TTY::Option + include State::Methods usage do program "dev" @@ -11,10 +14,16 @@ class Console long "--cluster string" end - option :task_definition do + option :task_family do + required + desc "ECS task definition family" + long "--task-family string" + end + + option :container_name do required - desc "ECS task definition" - long "--task-definition string" + desc "Container to target in the task definition" + long "--container-name string" end flag :help do @@ -40,35 +49,57 @@ def run private def interactive_console + find_latest_task_def start_task - sleep 10 + connect_to_instance + ensure stop_task end + def find_latest_task_def + resp = + client.describe_task_definition( + task_definition: params[:task_family] + ) + + @task_def = "#{params[:task_family]}:#{resp.task_definition.revision}" + + log.info "Using #{@task_def}" + end + def start_task overrides = { container_overrides: [ { - name: "certbot-renew", + name: params[:container_name], command: ["sleep", "infinity"] } ] } + whoami = `whoami` + + log.info "Starting task..." + resp = client.run_task( cluster: params[:cluster], - task_definition: params[:task_definition], + task_definition: @task_def, overrides: overrides, - started_by: "user-console/`whoami`" + started_by: "user-console/#{whoami}", + propagate_tags: "TASK_DEFINITION", ) tasks = resp.tasks - puts tasks.inspect @task_arn = tasks.first.task_arn + @container_instance_arn = tasks.first.container_instance_arn end def stop_task + return if @task_arn.nil? + + log.info "Stopping task..." + resp = client.stop_task( cluster: params[:cluster], @@ -77,10 +108,60 @@ def stop_task ) end + def connect_to_instance + resp = + client.describe_container_instances( + cluster: params[:cluster], + container_instances: [@container_instance_arn] + ) + + instance_id = resp.container_instances.first.ec2_instance_id + + script = <<~SCRIPT + docker run -it --rm \ + --pull always \ + --net host \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -e TASK_ARN=#{@task_arn} \ + ryansch/console-helper:latest + SCRIPT + + args = [ + "ssh", + "-t", + instance_id, + "sudo", + "sheltie" + ] + + args += script.lines + + begin + run_it + .subprocess( + args, + ) + rescue Subprocess::NonZeroExit => e + puts e.message + rescue Interrupt + end + end + def client return @client if defined?(@client) @client = Aws::ECS::Client.new end + + def state + return @state if defined?(@state) + + @state = + State.new(log_level: log_level) + end + + def log_level + Logger::INFO + end end From 8db49c0904f576e2ccc89f5360f874f8735a8886 Mon Sep 17 00:00:00 2001 From: Ryan Schlesinger Date: Fri, 10 Feb 2023 11:28:39 -0400 Subject: [PATCH 3/6] Pull awsvpc network info from SSM params --- bin/dev | 1 + lib/console.rb | 62 +++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/bin/dev b/bin/dev index 5eb6df5..dfe16f2 100755 --- a/bin/dev +++ b/bin/dev @@ -14,6 +14,7 @@ gemfile do gem 'pastel' gem 'tty-option' gem 'aws-sdk-ecs' + gem 'aws-sdk-ssm' gem 'nokogiri' end diff --git a/lib/console.rb b/lib/console.rb index 7238f4c..417a9f2 100644 --- a/lib/console.rb +++ b/lib/console.rb @@ -58,7 +58,7 @@ def interactive_console def find_latest_task_def resp = - client.describe_task_definition( + ecs_client.describe_task_definition( task_definition: params[:task_family] ) @@ -81,14 +81,23 @@ def start_task log.info "Starting task..." + run_task_args = { + cluster: params[:cluster], + task_definition: @task_def, + network_configuration: { + awsvpc_configuration: { + subnets: awsvpc_private_subnet_ids, + security_groups: awsvpc_security_group_ids + }, + }, + overrides: overrides, + started_by: "user-console/#{whoami}", + propagate_tags: "TASK_DEFINITION", + } + log.debug(run_task_args) + resp = - client.run_task( - cluster: params[:cluster], - task_definition: @task_def, - overrides: overrides, - started_by: "user-console/#{whoami}", - propagate_tags: "TASK_DEFINITION", - ) + ecs_client.run_task(run_task_args) tasks = resp.tasks @task_arn = tasks.first.task_arn @@ -101,16 +110,18 @@ def stop_task log.info "Stopping task..." resp = - client.stop_task( + ecs_client.stop_task( cluster: params[:cluster], task: @task_arn, reason: "user-console/stop" ) + + log.info "Done." end def connect_to_instance resp = - client.describe_container_instances( + ecs_client.describe_container_instances( cluster: params[:cluster], container_instances: [@container_instance_arn] ) @@ -123,6 +134,7 @@ def connect_to_instance --net host \ -v /var/run/docker.sock:/var/run/docker.sock \ -e TASK_ARN=#{@task_arn} \ + -e CONTAINER_NAME=#{params[:container_name]} \ ryansch/console-helper:latest SCRIPT @@ -147,13 +159,36 @@ def connect_to_instance end end - def client - return @client if defined?(@client) + def awsvpc_private_subnet_ids + resp = + ssm_client.get_parameter( + name: "/console/#{params[:cluster]}/#{params[:task_family]}/private_subnet_ids" + ) + JSON.parse(resp.parameter.value) + end + + def awsvpc_security_group_ids + resp = + ssm_client.get_parameter( + name: "/console/#{params[:cluster]}/#{params[:task_family]}/client_nodes_security_group_ids" + ) + JSON.parse(resp.parameter.value) + end + + def ecs_client + return @ecs_client if defined?(@ecs_client) - @client = + @ecs_client = Aws::ECS::Client.new end + def ssm_client + return @ssm_client if defined?(@ssm_client) + + @ssm_client = + Aws::SSM::Client.new + end + def state return @state if defined?(@state) @@ -163,5 +198,6 @@ def state def log_level Logger::INFO + # Logger::DEBUG end end From 7061e830896cf5d63ac11012bbf458b96531ca41 Mon Sep 17 00:00:00 2001 From: Ryan Schlesinger Date: Fri, 10 Feb 2023 13:59:20 -0400 Subject: [PATCH 4/6] Set all available console-helper env vars --- lib/console.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/console.rb b/lib/console.rb index 417a9f2..fbd0778 100644 --- a/lib/console.rb +++ b/lib/console.rb @@ -135,6 +135,9 @@ def connect_to_instance -v /var/run/docker.sock:/var/run/docker.sock \ -e TASK_ARN=#{@task_arn} \ -e CONTAINER_NAME=#{params[:container_name]} \ + -e DEBUG=false \ + -e BASH_SHELL=false \ + -e TASK_STATUS=true \ ryansch/console-helper:latest SCRIPT From ec0a26d00ccf12a53171eb20b2b09661b0cdca92 Mon Sep 17 00:00:00 2001 From: Ryan Schlesinger Date: Mon, 20 Mar 2023 12:51:49 -0300 Subject: [PATCH 5/6] Remove task_family from external config; more env vars --- lib/console.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/console.rb b/lib/console.rb index fbd0778..e23b68d 100644 --- a/lib/console.rb +++ b/lib/console.rb @@ -135,9 +135,9 @@ def connect_to_instance -v /var/run/docker.sock:/var/run/docker.sock \ -e TASK_ARN=#{@task_arn} \ -e CONTAINER_NAME=#{params[:container_name]} \ - -e DEBUG=false \ - -e BASH_SHELL=false \ - -e TASK_STATUS=true \ + -e DEBUG=#{ENV.fetch("DEBUG", "false")} \ + -e BASH_SHELL=#{ENV.fetch("BASH_SHELL", "false")} \ + -e TASK_STATUS=#{ENV.fetch("TASK_STATUS", "true")} \ ryansch/console-helper:latest SCRIPT @@ -165,7 +165,7 @@ def connect_to_instance def awsvpc_private_subnet_ids resp = ssm_client.get_parameter( - name: "/console/#{params[:cluster]}/#{params[:task_family]}/private_subnet_ids" + name: "/console/#{params[:cluster]}/private_subnet_ids" ) JSON.parse(resp.parameter.value) end @@ -173,7 +173,7 @@ def awsvpc_private_subnet_ids def awsvpc_security_group_ids resp = ssm_client.get_parameter( - name: "/console/#{params[:cluster]}/#{params[:task_family]}/client_nodes_security_group_ids" + name: "/console/#{params[:cluster]}/client_nodes_security_group_ids" ) JSON.parse(resp.parameter.value) end From dc61abd8afb74ebe3c50cda27d47e75cea004de8 Mon Sep 17 00:00:00 2001 From: Ryan Schlesinger Date: Tue, 21 Mar 2023 13:14:56 -0300 Subject: [PATCH 6/6] Add configurable log level, wait for container instances --- lib/console.rb | 47 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/lib/console.rb b/lib/console.rb index e23b68d..5331361 100644 --- a/lib/console.rb +++ b/lib/console.rb @@ -26,22 +26,37 @@ class Console long "--container-name string" end + option :log_level do + long "--log-level string" + desc "Sets the log level" + default :info + end + flag :help do short "-h" long "--help" desc "Print usage" end + flag :debug do + long "--debug" + desc "Shorthand to set the log level to DEBUG" + end + def run if params[:help] print help exit - elsif params.errors.any? + elsif !params.valid? puts params.errors.summary puts print help exit else + if params[:debug] + params[:log_level] = :debug + end + interactive_console end end @@ -94,14 +109,37 @@ def start_task started_by: "user-console/#{whoami}", propagate_tags: "TASK_DEFINITION", } + log.debug(run_task_args) resp = ecs_client.run_task(run_task_args) + + log.debug(resp) tasks = resp.tasks @task_arn = tasks.first.task_arn + @container_instance_arn = tasks.first.container_instance_arn + printed = false + + while @container_instance_arn.nil? + if !printed + printed = true + puts "Waiting for ECS task to populate container instances..." + end + + resp = + ecs_client.describe_tasks( + tasks: [ + @task_arn + ] + ) + log.debug(resp) + + sleep 1 + @container_instance_arn = tasks.first.container_instance_arn + end end def stop_task @@ -196,11 +234,6 @@ def state return @state if defined?(@state) @state = - State.new(log_level: log_level) - end - - def log_level - Logger::INFO - # Logger::DEBUG + State.new(log_level: params[:log_level]) end end