shiprails 0.1.0
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 +7 -0
- data/.DS_Store +0 -0
- data/.gitignore +19 -0
- data/.rbenv-vars.example +3 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +42 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/exe/.DS_Store +0 -0
- data/exe/port +6 -0
- data/exe/ship +6 -0
- data/lib/shiprails/application.rb +13 -0
- data/lib/shiprails/port.rb +53 -0
- data/lib/shiprails/ship/config.rb +121 -0
- data/lib/shiprails/ship/deploy.rb +191 -0
- data/lib/shiprails/ship/exec.rb +140 -0
- data/lib/shiprails/ship/install/.env.erb +23 -0
- data/lib/shiprails/ship/install/Dockerfile.erb +28 -0
- data/lib/shiprails/ship/install/Dockerfile.production.erb +29 -0
- data/lib/shiprails/ship/install/docker-compose.yml.erb +81 -0
- data/lib/shiprails/ship/install/shiprails.yml.erb +29 -0
- data/lib/shiprails/ship/install.rb +159 -0
- data/lib/shiprails/ship/scale.rb +81 -0
- data/lib/shiprails/ship/setup.rb +245 -0
- data/lib/shiprails/ship/task.rb +75 -0
- data/lib/shiprails/ship.rb +61 -0
- data/lib/shiprails/version.rb +3 -0
- data/lib/shiprails.rb +5 -0
- data/shiprails.gemspec +35 -0
- metadata +194 -0
@@ -0,0 +1,140 @@
|
|
1
|
+
require "active_support/all"
|
2
|
+
require "aws-sdk"
|
3
|
+
require "thor/group"
|
4
|
+
|
5
|
+
module Shiprails
|
6
|
+
class Ship < Thor
|
7
|
+
class Exec < Thor::Group
|
8
|
+
include Thor::Actions
|
9
|
+
class_option "path",
|
10
|
+
aliases: ["-p"],
|
11
|
+
default: ".",
|
12
|
+
desc: "Specify a configuration path"
|
13
|
+
class_option "environment",
|
14
|
+
default: "production",
|
15
|
+
desc: "Specify the environment"
|
16
|
+
class_option "region",
|
17
|
+
default: "us-west-2",
|
18
|
+
desc: "Specify the region"
|
19
|
+
class_option "service",
|
20
|
+
default: "app",
|
21
|
+
desc: "Specify the service name"
|
22
|
+
class_option "private-key",
|
23
|
+
default: "~/.ssh/aws.pem",
|
24
|
+
desc: "Specify the AWS SSH private key path"
|
25
|
+
|
26
|
+
def run_command
|
27
|
+
region = options['region']
|
28
|
+
service = options['service']
|
29
|
+
cluster_name = "#{project_name}_#{options['environment']}"
|
30
|
+
command_string = args.join ' '
|
31
|
+
ssh_private_key_path = options['private-key']
|
32
|
+
ecs_exec(region, cluster_name, service, command_string, ssh_private_key_path)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def configuration
|
38
|
+
YAML.load(File.read("#{options[:path]}/.shiprails.yml")).deep_symbolize_keys
|
39
|
+
end
|
40
|
+
|
41
|
+
def aws_access_key_id
|
42
|
+
@aws_access_key_id ||= ask "AWS Access Key ID", default: ENV.fetch("AWS_ACCESS_KEY_ID")
|
43
|
+
end
|
44
|
+
|
45
|
+
def aws_access_key_secret
|
46
|
+
@aws_access_key_secret ||= ask "AWS Access Key Secret", default: ENV.fetch("AWS_SECRET_ACCESS_KEY")
|
47
|
+
end
|
48
|
+
|
49
|
+
def project_name
|
50
|
+
configuration[:project_name]
|
51
|
+
end
|
52
|
+
|
53
|
+
def ecs_exec(region, cluster, service, command, ssh_private_key_path, ssh_user: 'ec2-user')
|
54
|
+
# we'll need to use both the ecs and ec2 apis
|
55
|
+
ecs = Aws::ECS::Client.new(region: region)
|
56
|
+
ec2 = Aws::EC2::Client.new(region: region)
|
57
|
+
|
58
|
+
# first we get the ARN of the task managed by the service
|
59
|
+
tasks_list = ecs.list_tasks({cluster: cluster, desired_status: 'RUNNING', service_name: service})
|
60
|
+
task_arn = tasks_list.task_arns.first
|
61
|
+
|
62
|
+
# using the ARN of the task, we can get the ARN of the container instance where its being deployed
|
63
|
+
task_descriptions = ecs.describe_tasks({cluster: cluster, tasks: [task_arn]})
|
64
|
+
task_definition_arn = task_descriptions.tasks.first.task_definition_arn
|
65
|
+
task_definition_name = task_definition_arn.split('/').last
|
66
|
+
container_instance_arn = task_descriptions.tasks.first.container_instance_arn
|
67
|
+
task_definition_description = ecs.describe_task_definition({task_definition: task_definition_name})
|
68
|
+
|
69
|
+
say "Setting up EC2 instance for SSH..."
|
70
|
+
# with the instance ARN let's grab the intance id
|
71
|
+
ec2_instance_id = ecs.describe_container_instances({cluster: cluster, container_instances: [container_instance_arn]}).container_instances.first.ec2_instance_id
|
72
|
+
ec2_instance = ec2.describe_instances({instance_ids: [ec2_instance_id]}).reservations.first.instances.first
|
73
|
+
|
74
|
+
# get its current security groups to restory later
|
75
|
+
security_group_ids = ec2_instance.security_groups.map(&:group_id)
|
76
|
+
|
77
|
+
# create new public ip
|
78
|
+
elastic_ip = ec2.allocate_address({ domain: "vpc" })
|
79
|
+
# link ip to ec2 instance
|
80
|
+
associate_address_response = ec2.associate_address({
|
81
|
+
allocation_id: elastic_ip.allocation_id,
|
82
|
+
instance_id: ec2_instance_id
|
83
|
+
})
|
84
|
+
# create security group for us
|
85
|
+
security_group_response = ec2.create_security_group({
|
86
|
+
group_name: "shiprails-exec-#{cluster}-#{Time.now.to_i}",
|
87
|
+
description: "SSH access to run interactive command (created by #{`whoami`.rstrip} via shiprails)",
|
88
|
+
vpc_id: ec2_instance.vpc_id
|
89
|
+
})
|
90
|
+
# get our public ip
|
91
|
+
my_ip_address = open('http://whatismyip.akamai.com').read
|
92
|
+
# authorize SSH access from our public ip
|
93
|
+
ec2.authorize_security_group_ingress({
|
94
|
+
group_id: security_group_response.group_id,
|
95
|
+
ip_protocol: "tcp",
|
96
|
+
from_port: 22,
|
97
|
+
to_port: 22,
|
98
|
+
cidr_ip: "#{my_ip_address}/32"
|
99
|
+
})
|
100
|
+
# add ec2 instance to our new security group
|
101
|
+
ec2.modify_instance_attribute({
|
102
|
+
instance_id: ec2_instance_id,
|
103
|
+
groups: security_group_ids + [security_group_response.group_id]
|
104
|
+
})
|
105
|
+
|
106
|
+
# build the command we'll run on the instance
|
107
|
+
command_array = ["docker run -it --rm"]
|
108
|
+
task_definition_description.task_definition.container_definitions.first.environment.each do |env|
|
109
|
+
command_array << "-e #{env.name}='#{env.value}'"
|
110
|
+
end
|
111
|
+
command_array << task_definition_description.task_definition.container_definitions.first.image
|
112
|
+
command_array << command
|
113
|
+
command_string = command_array.join ' '
|
114
|
+
|
115
|
+
say "Waiting for AWS to setup networking..."
|
116
|
+
sleep 5 # AWS just needs a little bit to setup networking
|
117
|
+
say "Connecting #{ssh_user}@#{elastic_ip.public_ip}..."
|
118
|
+
say "Executing: $ #{command_string}"
|
119
|
+
system "ssh -o ConnectTimeout=15 -o 'StrictHostKeyChecking no' -t -i #{ssh_private_key_path} #{ssh_user}@#{elastic_ip.public_ip} '#{command_string}'"
|
120
|
+
rescue => e
|
121
|
+
say "Error: #{e.message}", :red
|
122
|
+
ensure
|
123
|
+
say "Cleaning up SSH access..."
|
124
|
+
# restore original security groups
|
125
|
+
ec2.modify_instance_attribute({
|
126
|
+
instance_id: ec2_instance_id,
|
127
|
+
groups: security_group_ids
|
128
|
+
}) rescue nil
|
129
|
+
# remove our access security group
|
130
|
+
ec2.delete_security_group({ group_id: security_group_response.group_id }) rescue nil
|
131
|
+
# unlink ec2 instance from public ip
|
132
|
+
ec2.disassociate_address({ association_id: associate_address_response.association_id }) rescue nil
|
133
|
+
# release public ip address
|
134
|
+
ec2.release_address({ allocation_id: elastic_ip.allocation_id }) rescue nil
|
135
|
+
say "Done.", :green
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# https://github.com/ddollar/forego
|
2
|
+
|
3
|
+
COMPOSE_PROJECT_NAME=<%= project_name %>
|
4
|
+
APPLICATION_HOST=<%= application_host %>
|
5
|
+
ASSET_HOST=<%= application_host %>
|
6
|
+
APPLICATION_HOST=<%= application_host %>
|
7
|
+
|
8
|
+
PORT=3000
|
9
|
+
RACK_ENV=development
|
10
|
+
SECRET_KEY_BASE=development_secret
|
11
|
+
EXECJS_RUNTIME=Node
|
12
|
+
|
13
|
+
SMTP_ADDRESS=smtp.example.com
|
14
|
+
SMTP_DOMAIN=example.com
|
15
|
+
SMTP_PASSWORD=password
|
16
|
+
SMTP_USERNAME=username
|
17
|
+
|
18
|
+
RACK_MINI_PROFILER=1
|
19
|
+
WEB_CONCURRENCY=1
|
20
|
+
MAX_THREADS=4
|
21
|
+
|
22
|
+
FACEBOOK_APP_ID=FILL_ME_IN
|
23
|
+
FACEBOOK_SECRET=FILL_ME_IN
|
@@ -0,0 +1,28 @@
|
|
1
|
+
FROM ruby:<%= ruby_version %>-slim
|
2
|
+
|
3
|
+
RUN apt-get update -qq && apt-get install -y build-essential git-core
|
4
|
+
|
5
|
+
# for postgres
|
6
|
+
RUN apt-get install -y libpq-dev
|
7
|
+
|
8
|
+
# for nokogiri
|
9
|
+
RUN apt-get install -y libxml2-dev libxslt1-dev
|
10
|
+
|
11
|
+
# for capybara-webkit
|
12
|
+
RUN apt-get install -y libqt4-webkit libqt4-dev xvfb
|
13
|
+
|
14
|
+
# for a JS runtime
|
15
|
+
RUN apt-get install -y nodejs
|
16
|
+
|
17
|
+
# lighten up the image size
|
18
|
+
RUN rm -rf /var/lib/apt/lists/*
|
19
|
+
|
20
|
+
RUN gem install bundler --no-document
|
21
|
+
|
22
|
+
ENV APP_HOME /app
|
23
|
+
RUN mkdir $APP_HOME
|
24
|
+
WORKDIR $APP_HOME
|
25
|
+
|
26
|
+
ENV BUNDLE_PATH /bundle
|
27
|
+
|
28
|
+
ADD . $APP_HOME
|
@@ -0,0 +1,29 @@
|
|
1
|
+
FROM ruby:<%= ruby_version %>-slim
|
2
|
+
|
3
|
+
RUN apt-get update -qq && apt-get install -y build-essential git-core
|
4
|
+
|
5
|
+
# for postgres
|
6
|
+
RUN apt-get install -y libpq-dev
|
7
|
+
|
8
|
+
# for nokogiri
|
9
|
+
RUN apt-get install -y libxml2-dev libxslt1-dev
|
10
|
+
|
11
|
+
# for capybara-webkit
|
12
|
+
RUN apt-get install -y libqt4-webkit libqt4-dev xvfb
|
13
|
+
|
14
|
+
# for a JS runtime
|
15
|
+
RUN apt-get install -y nodejs
|
16
|
+
|
17
|
+
# lighten up the image size
|
18
|
+
RUN rm -rf /var/lib/apt/lists/*
|
19
|
+
|
20
|
+
RUN gem install bundler --no-document
|
21
|
+
|
22
|
+
ENV APP_HOME /app
|
23
|
+
RUN mkdir $APP_HOME
|
24
|
+
WORKDIR $APP_HOME
|
25
|
+
|
26
|
+
ADD Gemfile* $APP_HOME/
|
27
|
+
RUN bundle install
|
28
|
+
|
29
|
+
ADD . $APP_HOME
|
@@ -0,0 +1,81 @@
|
|
1
|
+
version: '2'
|
2
|
+
|
3
|
+
services:
|
4
|
+
memcached:
|
5
|
+
image: memcached
|
6
|
+
postgres:
|
7
|
+
environment:
|
8
|
+
LC_ALL: C.UTF-8
|
9
|
+
image: postgres
|
10
|
+
volumes:
|
11
|
+
- postgres-data:/var/lib/postgresql/data
|
12
|
+
redis:
|
13
|
+
command: redis-server --appendonly yes
|
14
|
+
image: redis
|
15
|
+
volumes:
|
16
|
+
- redis-data:/var/lib/redis
|
17
|
+
app: &app_base
|
18
|
+
build: .
|
19
|
+
command: ./bin/start
|
20
|
+
depends_on:
|
21
|
+
- memcached
|
22
|
+
- postgres
|
23
|
+
- redis
|
24
|
+
environment: &app_environment
|
25
|
+
# PostgreSQL Development Database:
|
26
|
+
DATABASE_URL: postgres://postgres:@postgres:5432/development?pool=25&encoding=unicode&schema_search_path=public
|
27
|
+
# memcached Development Cache:
|
28
|
+
MEMCACHED_URL: memcached:11211
|
29
|
+
# Redis Database:
|
30
|
+
REDIS_URL: redis://redis:6379
|
31
|
+
DOCKERIZED: 1
|
32
|
+
POOL_SIZE: 5
|
33
|
+
|
34
|
+
# Sidekiq configuration:
|
35
|
+
SIDEKIQ_CONCURRENCY: 5
|
36
|
+
SIDEKIQ_TIMEOUT: 10
|
37
|
+
|
38
|
+
# Enable the byebug debugging server - this can be overriden
|
39
|
+
# from the command line:
|
40
|
+
ENABLE_DEBUG_SERVER: 0
|
41
|
+
|
42
|
+
# Run the app in the 'development' environment:
|
43
|
+
RACK_ENV: development
|
44
|
+
env_file: .env
|
45
|
+
ports:
|
46
|
+
- "3000:3000"
|
47
|
+
stdin_open: true
|
48
|
+
tmpfs: /app/tmp
|
49
|
+
volumes:
|
50
|
+
- ".:/app"
|
51
|
+
- gems-data:/bundle
|
52
|
+
worker:
|
53
|
+
<<: *app_base
|
54
|
+
command: bundle exec sidekiq -C config/sidekiq.yml
|
55
|
+
ports: []
|
56
|
+
cable:
|
57
|
+
<<: *app_base
|
58
|
+
command: bundle exec puma --bind tcp://0.0.0.0:28080 cable/config.ru
|
59
|
+
ports:
|
60
|
+
- 28080:28080
|
61
|
+
# App Guard: Keeps running tests on a separate process:
|
62
|
+
test:
|
63
|
+
<<: *app_base # We copy from &app_base, and override:
|
64
|
+
command: bundle exec guard start --no-bundler-warning --no-interactions
|
65
|
+
environment:
|
66
|
+
<<: *app_environment
|
67
|
+
# PostgreSQL Test Database:
|
68
|
+
DATABASE_URL: postgres://postgres:@postgres:5432/test?pool=25&encoding=unicode&schema_search_path=public
|
69
|
+
|
70
|
+
# Run the app in the 'test' environment, instead of the default 'developent'
|
71
|
+
RACK_ENV: test
|
72
|
+
RAILS_ENV: test
|
73
|
+
ports: []
|
74
|
+
|
75
|
+
volumes:
|
76
|
+
gems-data:
|
77
|
+
driver: local
|
78
|
+
postgres-data:
|
79
|
+
driver: local
|
80
|
+
redis-data:
|
81
|
+
driver: local
|
@@ -0,0 +1,29 @@
|
|
1
|
+
config_s3_bucket: '<%= config_s3_bucket %>'
|
2
|
+
project_name: '<%= project_name %>'
|
3
|
+
services:
|
4
|
+
<%- services.each do |service| -%>
|
5
|
+
<%= service[:name] %>:
|
6
|
+
command: '<%= service[:command] %>'
|
7
|
+
image: '<%= service[:image] %>'
|
8
|
+
<%- if service[:ports].any? -%>
|
9
|
+
ports:
|
10
|
+
<%- service[:ports].each do |port| -%>
|
11
|
+
- <%= port %>
|
12
|
+
<%- end -%>
|
13
|
+
<%- end -%>
|
14
|
+
regions:
|
15
|
+
<%- service[:regions].each do |region, data| -%>
|
16
|
+
<%= region %>:
|
17
|
+
environments:
|
18
|
+
<%- environments.each do |environment| -%>
|
19
|
+
- <%= environment[:name] %>
|
20
|
+
<%- end -%>
|
21
|
+
<%- data.each do |k,v| -%>
|
22
|
+
<%= k %>: '<%= v %>'
|
23
|
+
<%- end -%>
|
24
|
+
<%- end -%>
|
25
|
+
resources:
|
26
|
+
<%- service[:resources].each do |k,v| -%>
|
27
|
+
<%= k %>: <%= v %>
|
28
|
+
<%- end -%>
|
29
|
+
<%- end -%>
|
@@ -0,0 +1,159 @@
|
|
1
|
+
require "active_support/all"
|
2
|
+
require "aws-sdk"
|
3
|
+
require "thor/group"
|
4
|
+
|
5
|
+
module Shiprails
|
6
|
+
class Ship < Thor
|
7
|
+
class Install < Thor::Group
|
8
|
+
include Thor::Actions
|
9
|
+
|
10
|
+
class_option "path",
|
11
|
+
aliases: ["-p"],
|
12
|
+
default: ".",
|
13
|
+
desc: "Specify a configuration path"
|
14
|
+
|
15
|
+
def self.source_root
|
16
|
+
File.expand_path("../install", __FILE__)
|
17
|
+
end
|
18
|
+
|
19
|
+
def application_host
|
20
|
+
"#{project_name}.dev"
|
21
|
+
end
|
22
|
+
|
23
|
+
no_commands {
|
24
|
+
def aws_access_key_id
|
25
|
+
@aws_access_key_id ||= ask "AWS Access Key ID", default: ENV.fetch("AWS_ACCESS_KEY_ID")
|
26
|
+
end
|
27
|
+
|
28
|
+
def aws_access_key_secret
|
29
|
+
@aws_access_key_secret ||= ask "AWS Access Key Secret", default: ENV.fetch("AWS_SECRET_ACCESS_KEY")
|
30
|
+
end
|
31
|
+
}
|
32
|
+
|
33
|
+
def config_s3_bucket
|
34
|
+
return @bucket_name unless @bucket_name.nil?
|
35
|
+
@_s3_client ||= Aws::S3::Client.new(region: ENV.fetch("AWS_REGION", "us-west-2"), access_key_id: aws_access_key_id, secret_access_key: aws_access_key_secret)
|
36
|
+
begin
|
37
|
+
bucket_name = "#{project_name}-config"
|
38
|
+
bucket_name = ask "S3 bucket name for configuration store", default: bucket_name
|
39
|
+
resp = @_s3_client.create_bucket({
|
40
|
+
bucket: bucket_name
|
41
|
+
})
|
42
|
+
rescue Aws::S3::Errors::BucketAlreadyExists
|
43
|
+
error "'#{bucket_name}' already exists"
|
44
|
+
retry
|
45
|
+
rescue Aws::S3::Errors::BucketAlreadyOwnedByYou
|
46
|
+
end
|
47
|
+
@bucket_name = bucket_name
|
48
|
+
end
|
49
|
+
|
50
|
+
def environments
|
51
|
+
environments = Dir.entries("#{Dir.getwd}/config/environments").grep(/\.rb$/).map { |fname| fname.chomp!(".rb") }.select{ |e| !['development', 'test'].include? e } rescue ['production']
|
52
|
+
environments ||= ['production']
|
53
|
+
@regions ||= ask("Which regions?", default: 'us-west-2').split(',')
|
54
|
+
environments.map do |environment|
|
55
|
+
{
|
56
|
+
name: environment,
|
57
|
+
regions: @regions
|
58
|
+
}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
no_commands {
|
63
|
+
def services
|
64
|
+
return @services unless @services.nil?
|
65
|
+
docker_compose = YAML.load(File.read("#{options[:path]}/docker-compose.yml")).deep_symbolize_keys
|
66
|
+
image_for_build = {}
|
67
|
+
regions_for_build = {}
|
68
|
+
@services = docker_compose[:services].map do |service_name, service|
|
69
|
+
next if [:test].include? service_name
|
70
|
+
if service[:image].nil?
|
71
|
+
build_name = service[:build]
|
72
|
+
build_name = "Dockerfile" if build_name == '.'
|
73
|
+
unless image = image_for_build[build_name]
|
74
|
+
image = service_name
|
75
|
+
image_for_build[build_name] = image
|
76
|
+
end
|
77
|
+
unless regions = regions_for_build[build_name]
|
78
|
+
regions = @regions.map do |region|
|
79
|
+
[region, {
|
80
|
+
repository_url: repository_url_for_region(region, service_name)
|
81
|
+
}]
|
82
|
+
end
|
83
|
+
regions_for_build[build_name] = regions
|
84
|
+
end
|
85
|
+
{
|
86
|
+
command: service[:command],
|
87
|
+
image: image,
|
88
|
+
name: service_name.to_s,
|
89
|
+
ports: (service[:ports] || []).map{ |port| port.split(":").last },
|
90
|
+
regions: regions,
|
91
|
+
resources: {
|
92
|
+
cpu_units: 256,
|
93
|
+
memory_units: 256
|
94
|
+
}
|
95
|
+
}
|
96
|
+
end
|
97
|
+
end.compact
|
98
|
+
end
|
99
|
+
|
100
|
+
def project_name
|
101
|
+
@project_name ||= ask "What's your project called?", default: File.basename(Dir.getwd)
|
102
|
+
end
|
103
|
+
|
104
|
+
def ruby_version
|
105
|
+
"#{RUBY_VERSION}"
|
106
|
+
end
|
107
|
+
}
|
108
|
+
|
109
|
+
def create_dockerfile
|
110
|
+
template("Dockerfile.erb", "#{options[:path]}/Dockerfile")
|
111
|
+
template("Dockerfile.production.erb", "#{options[:path]}/Dockerfile.production")
|
112
|
+
end
|
113
|
+
|
114
|
+
def create_dot_env
|
115
|
+
template(".env.erb", "#{options[:path]}/.env")
|
116
|
+
template(".env.erb", "#{options[:path]}/.env.example")
|
117
|
+
end
|
118
|
+
|
119
|
+
def ignore_dot_env
|
120
|
+
if File.exists?(".gitignore")
|
121
|
+
append_to_file(".gitignore", <<-EOF)
|
122
|
+
|
123
|
+
# Ignore Docker ENV
|
124
|
+
/.env
|
125
|
+
EOF
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def create_docker_compose
|
130
|
+
template("docker-compose.yml.erb", "#{options[:path]}/docker-compose.yml")
|
131
|
+
end
|
132
|
+
|
133
|
+
def create_configuration
|
134
|
+
template("shiprails.yml.erb", "#{options[:path]}/.shiprails.yml")
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
|
139
|
+
def repository_url_for_region(region, service_name)
|
140
|
+
@_ecr_client ||= Aws::ECR::Client.new(region: region, access_key_id: aws_access_key_id, secret_access_key: aws_access_key_secret)
|
141
|
+
resp = @_ecr_client.describe_repositories({}).to_h
|
142
|
+
say "Amazon EC2 Container Registry (ECR) for #{project_name}_#{service_name}?"
|
143
|
+
choices = ["CREATE NEW REGISTRY"] + resp[:repositories].map{|r| "#{r[:repository_name]} (#{r[:repository_uri]})" }
|
144
|
+
choices = choices.map.with_index{ |a, i| [i+1, *a]}
|
145
|
+
print_table choices
|
146
|
+
selection = ask("Pick one:").to_i
|
147
|
+
if selection == 1
|
148
|
+
resp = @_ecr_client.create_repository({
|
149
|
+
repository_name: "#{project_name}/#{service_name}",
|
150
|
+
}).to_h
|
151
|
+
resp[:repository][:repository_uri]
|
152
|
+
else
|
153
|
+
resp[:repositories][selection - 2][:repository_uri]
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require "active_support/all"
|
2
|
+
require "aws-sdk"
|
3
|
+
require "git"
|
4
|
+
require "thor/group"
|
5
|
+
|
6
|
+
module Shiprails
|
7
|
+
class Ship < Thor
|
8
|
+
class Scale < Thor::Group
|
9
|
+
include Thor::Actions
|
10
|
+
|
11
|
+
argument :method_name, type: :string
|
12
|
+
argument :environment, type: :string
|
13
|
+
argument :service, type: :string
|
14
|
+
argument :scale, type: :string
|
15
|
+
|
16
|
+
class_option "region",
|
17
|
+
desc: "Specify region"
|
18
|
+
class_option "path",
|
19
|
+
aliases: ["-p"],
|
20
|
+
default: ".",
|
21
|
+
desc: "Specify a configuration path"
|
22
|
+
|
23
|
+
def update_ecs_services
|
24
|
+
say "Setting ECS service #{service} scale=#{scale} in #{environment}..."
|
25
|
+
configuration[:services].each do |service_name, service|
|
26
|
+
next unless service_name.to_s == self.service
|
27
|
+
image_name = "#{project_name}_#{service_name}"
|
28
|
+
service[:regions].each do |region_name, region|
|
29
|
+
next unless options["region"].nil? or options["region"] == region_name.to_s
|
30
|
+
ecs = Aws::ECS::Client.new(region: region_name.to_s)
|
31
|
+
region[:environments].each do |environment_name|
|
32
|
+
next unless environment_name == self.environment
|
33
|
+
cluster_name = "#{project_name}_#{environment_name}"
|
34
|
+
task_name = "#{image_name}_#{environment_name}"
|
35
|
+
begin
|
36
|
+
task_definition_description = ecs.describe_task_definition({task_definition: task_name})
|
37
|
+
task_definition = task_definition_description.task_definition.to_hash
|
38
|
+
rescue Aws::ECS::Errors::ClientException => e
|
39
|
+
say "Missing ECS task for #{task_name}!", :red
|
40
|
+
say "Run `ship setup`", :red
|
41
|
+
exit
|
42
|
+
end
|
43
|
+
begin
|
44
|
+
service_response = ecs.update_service({
|
45
|
+
cluster: cluster_name,
|
46
|
+
service: service_name,
|
47
|
+
desired_count: scale
|
48
|
+
})
|
49
|
+
say "Set ECS service #{service_name} scale=#{scale} in #{environment} (#{region_name})...", :green
|
50
|
+
rescue Aws::ECS::Errors::ServiceNotFoundException, Aws::ECS::Errors::ServiceNotActiveException => e
|
51
|
+
say "Missing ECS service for #{task_name}!", :red
|
52
|
+
say "Run `ship setup`", :red
|
53
|
+
exit
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
say "ECS service updated.", :green
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def aws_access_key_id
|
64
|
+
@aws_access_key_id ||= ask "AWS Access Key ID", default: ENV.fetch("AWS_ACCESS_KEY_ID")
|
65
|
+
end
|
66
|
+
|
67
|
+
def aws_access_key_secret
|
68
|
+
@aws_access_key_secret ||= ask "AWS Access Key Secret", default: ENV.fetch("AWS_SECRET_ACCESS_KEY")
|
69
|
+
end
|
70
|
+
|
71
|
+
def configuration
|
72
|
+
YAML.load(File.read("#{options[:path]}/.shiprails.yml")).deep_symbolize_keys
|
73
|
+
end
|
74
|
+
|
75
|
+
def project_name
|
76
|
+
configuration[:project_name]
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|