shipitron 0.1.0 → 0.2.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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.dockerignore +4 -0
  3. data/.gitignore +4 -0
  4. data/.rspec +3 -0
  5. data/Dockerfile +60 -0
  6. data/Dockerfile.release +49 -0
  7. data/README.md +44 -10
  8. data/build_dev.sh +39 -0
  9. data/exe/shipitron +5 -0
  10. data/lib/shipitron/cli.rb +132 -0
  11. data/lib/shipitron/client/bootstrap_application.rb +35 -0
  12. data/lib/shipitron/client/create_ecs_services.rb +81 -0
  13. data/lib/shipitron/client/deploy_application.rb +34 -0
  14. data/lib/shipitron/client/ensure_deploy_not_running.rb +54 -0
  15. data/lib/shipitron/client/load_application_config.rb +41 -0
  16. data/lib/shipitron/client/load_templates.rb +45 -0
  17. data/lib/shipitron/client/register_ecs_task_definitions.rb +75 -0
  18. data/lib/shipitron/client/run_ecs_tasks.rb +141 -0
  19. data/lib/shipitron/consul_keys.rb +34 -0
  20. data/lib/shipitron/consul_lock.rb +28 -0
  21. data/lib/shipitron/docker_image.rb +23 -0
  22. data/lib/shipitron/ecs_client.rb +22 -0
  23. data/lib/shipitron/ecs_task_def.rb +17 -0
  24. data/lib/shipitron/fetch_bucket.rb +26 -0
  25. data/lib/shipitron/logger.rb +33 -0
  26. data/lib/shipitron/mustache_yaml_parser.rb +22 -0
  27. data/lib/shipitron/parse_templates.rb +32 -0
  28. data/lib/shipitron/post_build.rb +32 -0
  29. data/lib/shipitron/server/deploy_application.rb +75 -0
  30. data/lib/shipitron/server/docker/build_image.rb +25 -0
  31. data/lib/shipitron/server/docker/configure.rb +35 -0
  32. data/lib/shipitron/server/docker/push_image.rb +37 -0
  33. data/lib/shipitron/server/docker/run_build_script.rb +53 -0
  34. data/lib/shipitron/server/download_build_cache.rb +44 -0
  35. data/lib/shipitron/server/ecs_task_defs/map_parsed_templates.rb +31 -0
  36. data/lib/shipitron/server/ecs_task_defs/update_from_params.rb +42 -0
  37. data/lib/shipitron/server/ecs_task_defs/update_in_place.rb +68 -0
  38. data/lib/shipitron/server/git/clone_local_copy.rb +46 -0
  39. data/lib/shipitron/server/git/configure.rb +40 -0
  40. data/lib/shipitron/server/git/download_cache.rb +56 -0
  41. data/lib/shipitron/server/git/pull_repo.rb +30 -0
  42. data/lib/shipitron/server/git/update_cache.rb +52 -0
  43. data/lib/shipitron/server/git/upload_cache.rb +61 -0
  44. data/lib/shipitron/server/run_post_build.rb +79 -0
  45. data/lib/shipitron/server/transform_cli_args.rb +85 -0
  46. data/lib/shipitron/server/update_ecs_services.rb +105 -0
  47. data/lib/shipitron/server/update_ecs_task_definitions.rb +47 -0
  48. data/lib/shipitron/server/upload_build_cache.rb +43 -0
  49. data/lib/shipitron/smash.rb +10 -0
  50. data/lib/shipitron/version.rb +1 -1
  51. data/lib/shipitron.rb +38 -0
  52. data/scripts/docker-entrypoint.sh +18 -0
  53. data/scripts/fetch-bundler-data.sh +16 -0
  54. data/shipitron.gemspec +14 -0
  55. metadata +236 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7095a94bea4704f586b521f342b71374ebf80324
4
- data.tar.gz: bcb1526aefa4b5033e41e8a63bafb46753561e42
3
+ metadata.gz: 26404f4e8acd7b859cc8dc2e74a399ccb08ff293
4
+ data.tar.gz: 5654cccd96d69d418e468f1a95217d89000f6edf
5
5
  SHA512:
6
- metadata.gz: e2046e7a26028f1f3952390b0d5bd032fe32aaaa0f93c2982a1ed7bfcff8b14dfd932490315c1b34417cf7c976b3bcaa5bbca4024e25c394c3554dde442d079f
7
- data.tar.gz: 16d2caab5ed2026e4886d1a8ec6fddbdbadd587a751f19d73976f88f0c90f331534bc63f2a1fe1bb160cfb74aa3a52ba7656d2fd03db12ed4884cc90f1b3c9d4
6
+ metadata.gz: 24a5b2934c357f1204d315c32c7fcba122aad527e165feb4015c5b44f77335f7e7ed5226833216417da210711077204ce9d93f0c7d779ca454781be8713f40ff
7
+ data.tar.gz: b1978b9a83fae96d8c7d992253f46e0d4ebd87a3943080bdb0d697c36677100a6a27498376d48b415eb58ea690528abc38b6a5080b4eb5045f4ef6cafe97adea
data/.dockerignore ADDED
@@ -0,0 +1,4 @@
1
+ /shipitron/
2
+ /pkg/
3
+ Gemfile.lock
4
+ /tmp/
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ /shipitron/
2
+ /pkg/
3
+ Gemfile.lock
4
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --require spec_helper
3
+ --format Fivemat
data/Dockerfile ADDED
@@ -0,0 +1,60 @@
1
+ FROM ruby:2.3.1-alpine
2
+ MAINTAINER Ryan Schlesinger <ryan@outstand.com>
3
+
4
+ RUN addgroup -S shipitron && \
5
+ adduser -S -G shipitron shipitron && \
6
+ addgroup -g 1101 docker && \
7
+ addgroup shipitron docker
8
+
9
+ ENV GOSU_VERSION 1.9
10
+ ENV DUMB_INIT_VERSION 1.1.3
11
+
12
+ RUN apk add --no-cache ca-certificates gnupg && \
13
+ mkdir -p /tmp/build && \
14
+ cd /tmp/build && \
15
+ gpg --keyserver pool.sks-keyservers.net --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4 && \
16
+ curl -o gosu -L "https://github.com/tianon/gosu/releases/download/${GOSU_VERSION}/gosu-amd64" && \
17
+ curl -o gosu.asc -L "https://github.com/tianon/gosu/releases/download/${GOSU_VERSION}/gosu-amd64.asc" && \
18
+ gpg --verify gosu.asc && \
19
+ chmod +x gosu && \
20
+ cp gosu /bin/gosu && \
21
+ curl -O -L https://github.com/Yelp/dumb-init/releases/download/v${DUMB_INIT_VERSION}/dumb-init_${DUMB_INIT_VERSION}_amd64 && \
22
+ curl -O -L https://github.com/Yelp/dumb-init/releases/download/v${DUMB_INIT_VERSION}/sha256sums && \
23
+ grep dumb-init_${DUMB_INIT_VERSION}_amd64$ sha256sums | sha256sum -c && \
24
+ chmod +x dumb-init_${DUMB_INIT_VERSION}_amd64 && \
25
+ cp dumb-init_${DUMB_INIT_VERSION}_amd64 /bin/dumb-init && \
26
+ cd /tmp && \
27
+ rm -rf /tmp/build && \
28
+ apk del gnupg && \
29
+ rm -rf /root/.gnupg
30
+
31
+ RUN apk add --no-cache \
32
+ build-base \
33
+ git \
34
+ openssh \
35
+ perl \
36
+ bash
37
+
38
+ ENV USE_BUNDLE_EXEC true
39
+ ENV BUNDLE_GEMFILE /shipitron/Gemfile
40
+
41
+ WORKDIR /app
42
+ COPY Gemfile shipitron.gemspec /shipitron/
43
+ COPY lib/shipitron/version.rb /shipitron/lib/shipitron/
44
+ COPY scripts/fetch-bundler-data.sh /shipitron/scripts/fetch-bundler-data.sh
45
+
46
+ ARG bundler_data_host
47
+ RUN /shipitron/scripts/fetch-bundler-data.sh ${bundler_data_host} && \
48
+ (bundle check || bundle install) && \
49
+ git config --global push.default simple
50
+ COPY . /shipitron/
51
+ RUN ln -s /shipitron/exe/shipitron /usr/local/bin/shipitron && \
52
+ mkdir -p /home/shipitron/.ssh && \
53
+ chown shipitron:shipitron /home/shipitron/.ssh && \
54
+ chmod 700 /home/shipitron/.ssh
55
+
56
+ COPY scripts/docker-entrypoint.sh /docker-entrypoint.sh
57
+
58
+ ENV DUMB_INIT_SETSID 0
59
+ ENTRYPOINT ["/docker-entrypoint.sh"]
60
+ CMD ["help"]
@@ -0,0 +1,49 @@
1
+ FROM ruby:2.3.1-alpine
2
+ MAINTAINER Ryan Schlesinger <ryan@outstand.com>
3
+
4
+ RUN addgroup -S shipitron && \
5
+ adduser -S -G shipitron shipitron && \
6
+ addgroup -g 1101 docker && \
7
+ addgroup shipitron docker
8
+
9
+ ENV GOSU_VERSION 1.9
10
+ ENV DUMB_INIT_VERSION 1.1.3
11
+
12
+ RUN apk add --no-cache ca-certificates gnupg && \
13
+ mkdir -p /tmp/build && \
14
+ cd /tmp/build && \
15
+ gpg --keyserver pool.sks-keyservers.net --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4 && \
16
+ curl -o gosu -L "https://github.com/tianon/gosu/releases/download/${GOSU_VERSION}/gosu-amd64" && \
17
+ curl -o gosu.asc -L "https://github.com/tianon/gosu/releases/download/${GOSU_VERSION}/gosu-amd64.asc" && \
18
+ gpg --verify gosu.asc && \
19
+ chmod +x gosu && \
20
+ cp gosu /bin/gosu && \
21
+ curl -O -L https://github.com/Yelp/dumb-init/releases/download/v${DUMB_INIT_VERSION}/dumb-init_${DUMB_INIT_VERSION}_amd64 && \
22
+ curl -O -L https://github.com/Yelp/dumb-init/releases/download/v${DUMB_INIT_VERSION}/sha256sums && \
23
+ grep dumb-init_${DUMB_INIT_VERSION}_amd64$ sha256sums | sha256sum -c && \
24
+ chmod +x dumb-init_${DUMB_INIT_VERSION}_amd64 && \
25
+ cp dumb-init_${DUMB_INIT_VERSION}_amd64 /bin/dumb-init && \
26
+ cd /tmp && \
27
+ rm -rf /tmp/build && \
28
+ apk del gnupg && \
29
+ rm -rf /root/.gnupg
30
+
31
+ RUN apk add --no-cache \
32
+ git \
33
+ perl \
34
+ bash
35
+
36
+ WORKDIR /app
37
+
38
+ ENV SHIPITRON_VERSION=0.2.0
39
+
40
+ RUN gem install shipitron -v ${SHIPITRON_VERSION} && \
41
+ mkdir -p /home/shipitron/.ssh && \
42
+ chown shipitron:shipitron /home/shipitron/.ssh && \
43
+ chmod 700 /home/shipitron/.ssh
44
+
45
+ COPY scripts/docker-entrypoint.sh /docker-entrypoint.sh
46
+
47
+ ENV DUMB_INIT_SETSID 0
48
+ ENTRYPOINT ["/docker-entrypoint.sh"]
49
+ CMD ["help"]
data/README.md CHANGED
@@ -1,13 +1,47 @@
1
1
  # shipitron
2
2
  A deployment tool for use with Docker and ECS
3
3
 
4
- ## TODO
5
- - [ ] user runs `docker run -it --rm outstand/shipit deploy production`
6
- - [ ] shipit container calls `RunTask` to schedule the actual build
7
- - [ ] ECS runs `docker run -t outstand/shipit-server deploy production`
8
- - [ ] shipit container pulls git updates (pull cache from S3 in v2)
9
- - [ ] shipit container calls `build-prod.sh` script to build production container
10
- - [ ] `docker push`
11
- - [ ] `RegisterTaskDefinition`
12
- - [ ] `RunTask` (`rake db:migrate`) or `docker run`
13
- - [ ] `UpdateService` (updates service to use new task definition)
4
+ ## Usage
5
+
6
+ Example config file:
7
+ ```yaml
8
+ applications:
9
+ dummy-app:
10
+ repository: git@github.com:outstand/dummy-app
11
+ cache_bucket: bucket
12
+ image_name: outstand/dummy-app
13
+ build_script: shipitron/build.sh
14
+ post_builds:
15
+ - ecs_task: dummy-app
16
+ container_name: dummy-app
17
+ command: echo postbuild
18
+ ecs_clusters:
19
+ - name: us-east-1-prod-blue
20
+ region: us-east-1
21
+ - name: us-east-1-prod-green
22
+ region: us-east-1
23
+ shipitron_task: shipitron
24
+ ecs_task_defs:
25
+ - dummy-app
26
+ ecs_services:
27
+ - dummy-app
28
+ ```
29
+
30
+ - Create shipitron.yml file
31
+ - `docker run -it --rm -v shipitron.yml:/shipitron/config/shipitron.yml outstand/shipitron:<version> deploy <app>`
32
+
33
+ ## Development
34
+
35
+ - `docker volume create --name shipitron_fog`
36
+ - `./build_dev.sh`
37
+ - `docker run -it --rm -v $(pwd):/shipitron -v shipitron_fog:/fog -e FOG_LOCAL=true outstand/shipitron:dev rspec spec` to run specs
38
+ - `docker run -it --rm -v $(pwd):/shipitron outstand/shipitron:dev deploy <app>` to run client side
39
+ - `docker run -it --rm -v $(pwd):/shipitron outstand/shipitron:dev deploy <app> --simulate` to get the arguments for `server_deploy` below
40
+ - `docker run -it --rm --dns 10.10.10.2 -v /path/to/shipitron:/shipitron -v $(pwd):/app -v /bin/docker:/bin/docker -v /var/run/docker.sock:/var/run/docker.sock -v shipitron_fog:/fog -e FOG_LOCAL=true -e CONSUL_HOST=consul outstand/shipitron:dev server_deploy --name dummy-app --repository git@github.com:outstand/dummy-app --bucket outstand-shipitron --image-name outstand/dummy-app --region us-east-1 --cluster-name us-east-1-prod-blue --ecs-task-defs dummy-app --ecs-services dummy-app --build-script shipitron/build.sh --post-builds 'ecs_task:dummy-app,container_name:dummy-app,command:echo postbuild' --ecs-task-def-templates LS0tCmZhbWlseTogZHVtbXktYXBwCmNvbnRhaW5lcl9kZWZpbml0aW9uczoKICAtIG5hbWU6IGR1bW15LWFwcAogICAgaW1hZ2U6IG91dHN0YW5kL2R1bW15LWFwcDp7e3RhZ319CiAgICBtZW1vcnk6IDEyOAogICAgZXNzZW50aWFsOiB0cnVlCiAgICBwb3J0X21hcHBpbmdzOgogICAgICAtIGNvbnRhaW5lcl9wb3J0OiA4MAogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gbmFtZTogU0VSVklDRV84MF9OQU1FCiAgICAgICAgdmFsdWU6IGR1bW15Cg== --ecs-service-templates LS0tCmNsdXN0ZXI6IHt7Y2x1c3Rlcn19CnNlcnZpY2VfbmFtZTogZHVtbXktYXBwCnRhc2tfZGVmaW5pdGlvbjogZHVtbXktYXBwe3tyZXZpc2lvbn19CmRlc2lyZWRfY291bnQ6IHt7Y291bnR9fQojcm9sZToge3tyb2xlfX0KZGVwbG95bWVudF9jb25maWd1cmF0aW9uOgogIG1heGltdW1fcGVyY2VudDogMjAwCiAgbWluaW11bV9oZWFsdGh5X3BlcmNlbnQ6IDUwCg== --debug` to run server side (dummy-app is an example)
41
+
42
+ To release a new version:
43
+ - Update the version number in `version.rb` and `Dockerfile.release` and commit the result.
44
+ - `./build_dev.sh`
45
+ - `docker run -it --rm -v ~/.gitconfig:/root/.gitconfig -v ~/.gitconfig.user:/root/.gitconfig.user -v ~/.ssh/id_rsa:/root/.ssh/id_rsa -v ~/.gem:/root/.gem -w /shipitron outstand/shipitron:dev rake release`
46
+ - `docker build -t outstand/shipitron:VERSION -f Dockerfile.release .`
47
+ - `docker push outstand/shipitron:VERSION`
data/build_dev.sh ADDED
@@ -0,0 +1,39 @@
1
+ #!/bin/bash
2
+ set -e -x
3
+
4
+ build_image=outstand/shipitron:dev
5
+ dockerfile=Dockerfile
6
+ bundler_data_dir=tmp
7
+
8
+ bundler_data_container=''
9
+ tar_container=''
10
+
11
+ function cleanup {
12
+ if [ -n "$bundler_data_container" ]; then
13
+ docker stop bundler-data
14
+ docker rm -fv bundler-data
15
+ fi
16
+
17
+ if [ -n "$tar_container" ]; then
18
+ docker rm -fv ${tar_container}
19
+ fi
20
+
21
+ rm -f ${bundler_data_dir}/cidfile
22
+ }
23
+
24
+ trap cleanup EXIT
25
+
26
+ build_args=''
27
+ mkdir -p ${bundler_data_dir}
28
+
29
+ if [ -f $(pwd)/${bundler_data_dir}/bundler-data.tar.gz ]; then
30
+ docker run --name bundler-data -v $(pwd)/${bundler_data_dir}/bundler-data.tar.gz:/usr/share/nginx/html/bundler-data.tar.gz:ro -d nginx:stable-alpine
31
+ bundler_data_container=bundler-data
32
+ build_args="--build-arg bundler_data_host=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' bundler-data)"
33
+ fi
34
+
35
+ docker build -t ${build_image} -f ${dockerfile} ${build_args} .
36
+
37
+ docker run -t --cidfile=${bundler_data_dir}/cidfile -w /usr/local/bundle ${build_image} tar -zcf /tmp/bundler-data.tar.gz .
38
+ tar_container=$(cat ${bundler_data_dir}/cidfile)
39
+ docker cp ${tar_container}:/tmp/bundler-data.tar.gz ${bundler_data_dir}/bundler-data.tar.gz
data/exe/shipitron ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'shipitron/cli'
4
+
5
+ Shipitron::CLI.start
@@ -0,0 +1,132 @@
1
+ require 'thor'
2
+ require 'shipitron'
3
+
4
+ module Shipitron
5
+ class CLI < Thor
6
+ desc 'version', 'Print out the version string'
7
+ def version
8
+ require 'shipitron/version'
9
+ say Shipitron::VERSION.to_s
10
+ end
11
+
12
+ desc 'deploy <app>', 'Deploys the app'
13
+ option :config_file, default: 'shipitron/config.yml'
14
+ option :secrets_file, default: 'shipitron/secrets.yml'
15
+ option :ember, type: :boolean, default: false
16
+ option :ember_only, type: :boolean, default: false
17
+ option :debug, type: :boolean, default: false
18
+ option :simulate, type: :boolean, default: false
19
+ def deploy(app)
20
+ setup(
21
+ config_file: options[:config_file],
22
+ secrets_file: options[:secrets_file]
23
+ )
24
+
25
+ require 'shipitron/client/deploy_application'
26
+ result = Client::DeployApplication.call(
27
+ application: app,
28
+ simulate: options[:simulate]
29
+ )
30
+
31
+ if result.failure?
32
+ result.errors.each do |error|
33
+ Logger.fatal error
34
+ end
35
+ Logger.fatal 'Deploy failed.'
36
+ end
37
+ end
38
+
39
+ desc 'server_deploy', 'Server-side component of deploy'
40
+ option :name, required: true
41
+ option :repository, required: true
42
+ option :branch, default: 'master'
43
+ option :bucket, required: true
44
+ option :image_name, required: true
45
+ option :region, required: true
46
+ option :cluster_name, required: true
47
+ option :ecs_task_defs, type: :array, required: true
48
+ option :ecs_task_def_templates, type: :array, default: []
49
+ option :ecs_services, type: :array, required: true
50
+ option :ecs_service_templates, type: :array, default: []
51
+ option :build_script, default: nil
52
+ option :post_builds, type: :array
53
+ option :secrets_file, default: 'shipitron/secrets.yml'
54
+ option :debug, type: :boolean, default: false
55
+ def server_deploy
56
+ setup(
57
+ secrets_file: options[:secrets_file]
58
+ )
59
+
60
+ require 'shipitron/server/transform_cli_args'
61
+ cli_args = Server::TransformCliArgs.call!(
62
+ application: options[:name],
63
+ repository_url: options[:repository],
64
+ repository_branch: options[:branch],
65
+ s3_cache_bucket: options[:bucket],
66
+ image_name: options[:image_name],
67
+ region: options[:region],
68
+ cluster_name: options[:cluster_name],
69
+ ecs_task_defs: options[:ecs_task_defs],
70
+ ecs_task_def_templates: options[:ecs_task_def_templates],
71
+ ecs_services: options[:ecs_services],
72
+ ecs_service_templates: options[:ecs_service_templates],
73
+ build_script: options[:build_script],
74
+ post_builds: options[:post_builds]
75
+ ).cli_args
76
+
77
+ require 'shipitron/server/deploy_application'
78
+ result = Server::DeployApplication.call(
79
+ cli_args
80
+ )
81
+
82
+ if result.failure?
83
+ result.errors.each do |error|
84
+ Logger.fatal error
85
+ end
86
+ Logger.fatal 'Deploy failed.'
87
+ end
88
+ end
89
+
90
+ desc 'bootstrap <app>', 'Bootstrap ECS task definitions and services'
91
+ option :region, required: true
92
+ option :cluster_name, required: true
93
+ option :service_count, type: :numeric, default: 3
94
+ option :task_def_dir, default: 'shipitron/ecs_task_defs'
95
+ option :service_dir, default: 'shipitron/ecs_services'
96
+ option :secrets_file, default: 'shipitron/secrets.yml'
97
+ option :debug, type: :boolean, default: false
98
+ def bootstrap(app)
99
+ setup(
100
+ secrets_file: options[:secrets_file]
101
+ )
102
+
103
+ require 'shipitron/client/bootstrap_application'
104
+ result = Client::BootstrapApplication.call(
105
+ application: app,
106
+ region: options[:region],
107
+ cluster_name: options[:cluster_name],
108
+ service_count: options[:service_count],
109
+ task_def_directory: options[:task_def_dir],
110
+ service_directory: options[:service_dir]
111
+ )
112
+
113
+ if result.failure?
114
+ result.errors.each do |error|
115
+ Logger.fatal error
116
+ end
117
+ Logger.fatal 'Bootstrap failed.'
118
+ end
119
+ end
120
+
121
+ private
122
+ def setup(config_file:nil, secrets_file:nil)
123
+ $stdout.sync = true
124
+ if options[:debug] == false
125
+ Logger.level = :info
126
+ end
127
+
128
+ Shipitron.config_file = config_file unless config_file.nil?
129
+ Shipitron.secrets_file = secrets_file unless secrets_file.nil?
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,35 @@
1
+ require 'shipitron'
2
+ require 'shipitron/client/register_ecs_task_definitions'
3
+ require 'shipitron/client/create_ecs_services'
4
+
5
+ module Shipitron
6
+ module Client
7
+ class BootstrapApplication
8
+ include Metaractor
9
+ include Interactor::Organizer
10
+
11
+ required :application
12
+ required :region
13
+ required :cluster_name
14
+ required :service_count
15
+ required :task_def_directory
16
+ required :service_directory
17
+
18
+ organize [
19
+ RegisterEcsTaskDefinitions,
20
+ CreateEcsServices
21
+ ]
22
+
23
+ def call
24
+ Logger.info "==> Bootstrapping #{application}"
25
+ super
26
+ Logger.info "==> Done"
27
+ end
28
+
29
+ private
30
+ def application
31
+ context.application
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,81 @@
1
+ require 'shipitron'
2
+ require 'shipitron/ecs_client'
3
+ require 'shipitron/mustache_yaml_parser'
4
+
5
+ module Shipitron
6
+ module Client
7
+ class CreateEcsServices
8
+ include Metaractor
9
+ include EcsClient
10
+
11
+ required :region
12
+ required :service_directory
13
+ required :cluster_name
14
+ required :service_count
15
+
16
+ def call
17
+ Logger.info 'Creating ECS services'
18
+
19
+ service_defs = Pathname.new(service_directory)
20
+ unless service_defs.directory?
21
+ fail_with_error!(
22
+ message: "service directory '#{service_directory}' does not exist"
23
+ )
24
+ end
25
+
26
+ service_defs.find do |path|
27
+ next if path.directory?
28
+
29
+ service_def = Smash.load(
30
+ path.to_s,
31
+ parser: MustacheYamlParser.new(
32
+ context: {
33
+ cluster: cluster_name,
34
+ revision: nil, # ECS will default to latest ACTIVE
35
+ count: service_count
36
+ }
37
+ )
38
+ ).merge(
39
+ client_token: SecureRandom.uuid
40
+ )
41
+
42
+ Logger.info "Creating service '#{service_def.service_name}'"
43
+ Logger.debug "Service definition: #{service_def.to_h}"
44
+ begin
45
+ ecs_client(region: region).create_service(
46
+ service_def.to_h
47
+ )
48
+ rescue Aws::ECS::Errors::InvalidParameterException => e
49
+ raise if e.message != 'Creation of service was not idempotent.'
50
+
51
+ Logger.info "Service '#{service_def.service_name}' already exists."
52
+ end
53
+ end
54
+
55
+ Logger.info 'Done'
56
+ rescue Aws::ECS::Errors::ServiceError => e
57
+ fail_with_errors!(messages: [
58
+ "Error: #{e.message}",
59
+ e.backtrace.join("\n")
60
+ ])
61
+ end
62
+
63
+ private
64
+ def region
65
+ context.region
66
+ end
67
+
68
+ def service_directory
69
+ context.service_directory
70
+ end
71
+
72
+ def cluster_name
73
+ context.cluster_name
74
+ end
75
+
76
+ def service_count
77
+ context.service_count
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,34 @@
1
+ require 'shipitron'
2
+ require 'shipitron/client/load_application_config'
3
+ require 'shipitron/client/load_templates'
4
+ require 'shipitron/client/ensure_deploy_not_running'
5
+ require 'shipitron/client/run_ecs_tasks'
6
+
7
+ module Shipitron
8
+ module Client
9
+ class DeployApplication
10
+ include Metaractor
11
+ include Interactor::Organizer
12
+
13
+ required :application
14
+
15
+ organize [
16
+ LoadApplicationConfig,
17
+ LoadTemplates,
18
+ EnsureDeployNotRunning,
19
+ RunEcsTasks
20
+ ]
21
+
22
+ def call
23
+ Logger.info "==> Deploying #{application}"
24
+ super
25
+ Logger.info "==> Done"
26
+ end
27
+
28
+ private
29
+ def application
30
+ context.application
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,54 @@
1
+ require 'shipitron'
2
+ require 'shipitron/ecs_client'
3
+
4
+ # Note: This is a best effort client side check to make sure there
5
+ # isn't a deploy running. The server side check has more guarantees.
6
+
7
+ module Shipitron
8
+ module Client
9
+ class EnsureDeployNotRunning
10
+ include Metaractor
11
+ include EcsClient
12
+
13
+ required :clusters
14
+ optional :simulate
15
+
16
+ def call
17
+ return if simulate?
18
+
19
+ clusters.each do |cluster|
20
+ %w[PENDING RUNNING].each do |status|
21
+ begin
22
+ response = ecs_client(region: cluster.region).list_tasks(
23
+ cluster: cluster.name,
24
+ started_by: 'shipitron',
25
+ max_results: 1,
26
+ desired_status: status
27
+ )
28
+ if !response.task_arns.empty?
29
+ fail_with_errors!(messages: [
30
+ 'Shipitron says "THERE CAN BE ONLY ONE"',
31
+ 'Deploy is already running.'
32
+ ])
33
+ end
34
+ rescue Aws::ECS::Errors::ClusterNotFoundException
35
+ fail_with_errors!(messages: [
36
+ 'Shipitron says "PUNY HUMAN IS MISSING A CLUSTER"',
37
+ "Cluster '#{cluster.name}' not found in region #{cluster.region}."
38
+ ])
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ private
45
+ def clusters
46
+ context.clusters
47
+ end
48
+
49
+ def simulate?
50
+ context.simulate == true
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,41 @@
1
+ require 'shipitron'
2
+ require 'shipitron/post_build'
3
+
4
+ module Shipitron
5
+ module Client
6
+ class LoadApplicationConfig
7
+ include Metaractor
8
+
9
+ required :application
10
+
11
+ def call
12
+ context.repository_url = config.repository
13
+ context.s3_cache_bucket = config.cache_bucket
14
+ context.image_name = config.image_name
15
+ context.build_script = config.build_script
16
+ context.post_builds = begin
17
+ if config.post_builds.nil?
18
+ []
19
+ else
20
+ config.post_builds.map {|pb| PostBuild.new(pb) }
21
+ end
22
+ end
23
+ context.clusters = config.ecs_clusters
24
+ context.shipitron_task = config.shipitron_task
25
+ context.ecs_task_defs = config.ecs_task_defs
26
+ context.ecs_services = config.ecs_services
27
+ context.ecs_task_def_dir = config.ecs_task_def_dir
28
+ context.ecs_service_dir = config.ecs_service_dir
29
+ end
30
+
31
+ private
32
+ def application
33
+ context.application
34
+ end
35
+
36
+ def config
37
+ @config ||= Shipitron.config.applications[application]
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,45 @@
1
+ require 'shipitron'
2
+
3
+ module Shipitron
4
+ module Client
5
+ class LoadTemplates
6
+ include Metaractor
7
+
8
+ required :ecs_task_def_dir
9
+ required :ecs_service_dir
10
+
11
+ def call
12
+ context.ecs_task_def_templates = load_templates(ecs_task_def_dir)
13
+ context.ecs_service_templates = load_templates(ecs_service_dir)
14
+ end
15
+
16
+ private
17
+ def ecs_task_def_dir
18
+ context.ecs_task_def_dir
19
+ end
20
+
21
+ def ecs_service_dir
22
+ context.ecs_service_dir
23
+ end
24
+
25
+ def load_templates(dir)
26
+ search_path = Pathname.new(dir)
27
+ unless search_path.directory?
28
+ fail_with_error!(
29
+ message: "directory '#{dir}' does not exist"
30
+ )
31
+ end
32
+
33
+ templates = []
34
+ search_path.find do |path|
35
+ next if path.directory?
36
+
37
+ templates << path.read
38
+ end
39
+
40
+ Logger.debug "Templates loaded: #{templates.inspect}"
41
+ templates
42
+ end
43
+ end
44
+ end
45
+ end