ufo 1.2.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ff5b6f6bdcbb6495ea2cf2531698fe558246696e
4
- data.tar.gz: 0a206451dca821129ae71f77e14375c61e386c66
3
+ metadata.gz: 88ffa32343a3f2d384a43df599aab3521141bd03
4
+ data.tar.gz: d305e9b86ea4dbb552014b1c1ea4ee67d79dd4f6
5
5
  SHA512:
6
- metadata.gz: cea5a0c53eaf714b1185b529550aa24e51e2f0e6b0320d1879a4c3c5342362db60c7b0b26fdcd5bd61b17516764b12af2cf5acee96ed7ee8d787336b59169bcb
7
- data.tar.gz: 2041c3b1723d6fd113094a258f5adac6d5b5f8128a7ba1733cf7b26109b0e8130f59a5e9b0a0775990df26d144d44bd9f1839a015226cc494983ff1cd0959279
6
+ metadata.gz: 200363f4ff73473eb9ca774ded09805f459af00d01130a9a4e7f9dee41238b5d0522be360908ba63f56b480f33f7e171f6fd7f3267e49af00c529fc43916911f
7
+ data.tar.gz: 0c1c498e29e3669907137abc93dbb9aa5b914a764ef506ec310e395124950171d8c78b39dbe7dfdaae74e93df743d99dcc0e5e063b4925b5fb5d5a4675134fcc
data/CHANGELOG.md CHANGED
@@ -3,6 +3,12 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  This project *tries* to adhere to [Semantic Versioning](http://semver.org/), even before v1.0.
5
5
 
6
+ ## [1.5.0]
7
+ * add ufo ships command
8
+ * refactor code into modules: ecr, docker, tasks
9
+ * improve error message when task_definitions.rb evaluation errors
10
+ * rename --force option to --sure
11
+
6
12
  ## [1.2.0]
7
13
 
8
14
  * allow -h, --help, help options at the end of the command
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ufo (1.1.0)
4
+ ufo (1.2.0)
5
5
  aws-sdk
6
6
  colorize
7
7
  deep_merge
@@ -20,13 +20,13 @@ GEM
20
20
  minitest (~> 5.1)
21
21
  thread_safe (~> 0.3, >= 0.3.4)
22
22
  tzinfo (~> 1.1)
23
- aws-sdk (2.9.25)
24
- aws-sdk-resources (= 2.9.25)
25
- aws-sdk-core (2.9.25)
23
+ aws-sdk (2.9.28)
24
+ aws-sdk-resources (= 2.9.28)
25
+ aws-sdk-core (2.9.28)
26
26
  aws-sigv4 (~> 1.0)
27
27
  jmespath (~> 1.0)
28
- aws-sdk-resources (2.9.25)
29
- aws-sdk-core (= 2.9.25)
28
+ aws-sdk-resources (2.9.28)
29
+ aws-sdk-core (= 2.9.28)
30
30
  aws-sigv4 (1.0.0)
31
31
  builder (3.2.3)
32
32
  byebug (9.0.6)
@@ -37,7 +37,7 @@ GEM
37
37
  diff-lcs (1.3)
38
38
  docile (1.1.5)
39
39
  hashie (3.5.5)
40
- i18n (0.8.3)
40
+ i18n (0.8.4)
41
41
  jmespath (1.3.1)
42
42
  json (2.1.0)
43
43
  minitest (5.10.2)
data/README.md CHANGED
@@ -158,7 +158,7 @@ task_definition "hi-web-prod" do
158
158
  family: task_definition_name,
159
159
  # image: tongueroo/hi:ufo-[timestamp]=[sha]
160
160
  image: helper.full_image_name,
161
- environment: env_file('.env.prod')
161
+ environment: helper.env_file('.env.prod')
162
162
  name: "web",
163
163
  container_port: helper.dockerfile_port,
164
164
  command: ["bin/web"]
@@ -170,8 +170,8 @@ The task\_definitions.rb file has some special variables and helper methods avai
170
170
 
171
171
  * **helper.full\_image\_name** — The full docker image name that ufo builds. The “base” portion of the docker image name is defined in ufo/settings.yml. For example, the base portion is `tongueroo/hi` and the full image name is `tongueroo/hi:ufo-[timestamp]-[sha]`. The base name does not include the generated Docker tag, which contains a timestamp and git sha of the Dockerfile that is used.
172
172
  * **helper.dockerfile\_port** — Exposed port extracted from the Dockerfile of the project. 
173
- * **env_vars(text)** — This method takes a block of text that contains the env values in key=value format and converts that block of text to the proper task definition json format.
174
- * **env_file(path)** — This method takes an `.env` file which contains a simple key value list of environment variables and converts the list to the proper task definition json format.
173
+ * **helper.env_vars(text)** — This method takes a block of text that contains the env values in key=value format and converts that block of text to the proper task definition json format.
174
+ * **helper.env_file(path)** — This method takes an `.env` file which contains a simple key value list of environment variables and converts the list to the proper task definition json format.
175
175
 
176
176
  The 2 classes which provide these special helper methods are in [ufo/dsl.rb](https://github.com/tongueroo/ufo/blob/master/lib/ufo/dsl.rb) and [ufo/dsl/helper.rb](https://github.com/tongueroo/ufo/blob/master/lib/ufo/dsl/helper.rb). Refer to these classes for the full list of the special variables and methods.
177
177
 
@@ -184,9 +184,9 @@ This is easily accomplished with the `bin/deploy` wrapper script that the `ufo i
184
184
  ```bash
185
185
  #!/bin/bash -xe
186
186
 
187
- ufo ship hi-worker-prod --cluster stag --no-wait
188
- ufo ship hi-clock-prod --cluster stag --no-wait --no-docker
189
- ufo ship hi-web-prod --cluster stag --no-docker
187
+ ufo ship hi-worker-prod --cluster prod --no-wait
188
+ ufo ship hi-clock-prod --cluster prod --no-wait --no-docker
189
+ ufo ship hi-web-prod --cluster prod --no-docker
190
190
  ```
191
191
 
192
192
  The first `ufo ship hi-worker-prod` command build and ships docker image to ECS, but the following two `ufo ship` commands use the `--no-docker` flag to skip the `docker build` step. `ufo ship` will use the last built docker image as the image to be shipped. For those curious, this is stored in `ufo/docker_image_name_ufo.txt`.
@@ -196,7 +196,7 @@ The first `ufo ship hi-worker-prod` command build and ships docker image to ECS,
196
196
  Ufo assumes a convention that service\_name and the task\_name are the same. If you would like to override this convention then you can specify the task name.
197
197
 
198
198
  ```
199
- ufo ship hi-web-prod--task my-task
199
+ ufo ship hi-web-prod --task my-task
200
200
  ```
201
201
 
202
202
  This means that in the task_definition.rb you will also defined it with `my-task`. For example:
@@ -233,7 +233,7 @@ ufo tasks register # will register all genreated task definitinos in the ufo/out
233
233
  Skips all the build docker phases of a deploy sequence and only update the service with the task definitions.
234
234
 
235
235
  ```bash
236
- ufo ship hi-web-prod--no-docker
236
+ ufo ship hi-web-prod --no-docker
237
237
  ```
238
238
  Note if you use the `--no-docker` option you should ensure that you have already push a docker image to your docker register. Or else the task will not be able to spin up because the docker image does not exist. I recommend that you normally use `ufo ship` most of the time.
239
239
 
data/bin/ufo CHANGED
@@ -1,5 +1,12 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ # Trap ^C
4
+ Signal.trap("INT") {
5
+ puts "\nCtrl-C detected. Exiting..."
6
+ sleep 1
7
+ exit
8
+ }
9
+
3
10
  require 'rubygems'
4
11
 
5
12
  $:.unshift(File.expand_path('../../lib', __FILE__))
data/lib/ufo.rb CHANGED
@@ -1,28 +1,24 @@
1
- $:.unshift(File.expand_path("../", __FILE__))
2
- require "ufo/version"
3
- require "pp"
1
+ $:.unshift(File.expand_path('../', __FILE__))
2
+ require 'ufo/version'
4
3
  require 'deep_merge'
5
- require "colorize"
4
+ require 'colorize'
5
+ require 'fileutils'
6
6
 
7
7
  module Ufo
8
+ autoload :Defaults, 'ufo/defaults'
9
+ autoload :AwsServices, 'ufo/aws_services'
8
10
  autoload :Command, 'ufo/command'
9
11
  autoload :Settings, 'ufo/settings'
10
- autoload :PrettyTime, 'ufo/pretty_time'
11
- autoload :Execute, 'ufo/execute'
12
+ autoload :Util, 'ufo/util'
12
13
  autoload :Init, 'ufo/init'
13
- autoload :EcrAuth, 'ufo/ecr_auth'
14
14
  autoload :CLI, 'ufo/cli'
15
- autoload :DockerBuilder, 'ufo/docker_builder'
16
- autoload :DockerfileUpdater, 'ufo/dockerfile_updater'
17
- autoload :DockerCleaner, 'ufo/docker_cleaner'
18
- autoload :TasksBuilder, 'ufo/tasks_builder'
19
- autoload :TasksRegister, 'ufo/tasks_register'
20
15
  autoload :Ship, 'ufo/ship'
21
16
  autoload :Task, 'ufo/task'
22
- autoload :EcrCleaner, 'ufo/ecr_cleaner'
23
17
  autoload :Destroy, 'ufo/destroy'
18
+ autoload :DSL, 'ufo/dsl'
24
19
  autoload :Scale, 'ufo/scale'
25
- # modules
26
- autoload :Defaults, 'ufo/defaults'
27
- autoload :AwsServices, 'ufo/aws_services'
20
+
21
+ autoload :Docker, 'ufo/docker'
22
+ autoload :Ecr, 'ufo/ecr'
23
+ autoload :Tasks, 'ufo/tasks'
28
24
  end
data/lib/ufo/cli.rb CHANGED
@@ -3,63 +3,6 @@ require 'ufo/command'
3
3
  require 'ufo/cli/help'
4
4
 
5
5
  module Ufo
6
- class Docker < Command
7
- desc "build", "builds docker image"
8
- long_desc CLI::Help.docker_build
9
- option :push, type: :boolean, default: false
10
- def build
11
- builder = DockerBuilder.new(options)
12
- builder.build
13
- builder.push if options[:push]
14
- end
15
-
16
- desc "base", "builds docker image from Dockerfile.base and update current Dockerfile"
17
- long_desc CLI::Help.docker_base
18
- option :push, type: :boolean, default: true
19
- def base
20
- builder = DockerBuilder.new(options.dup.merge(
21
- image_namespace: "base",
22
- dockerfile: "Dockerfile.base"
23
- ))
24
- builder.build
25
- builder.push if options[:push]
26
- builder.update_dockerfile
27
- DockerCleaner.new(builder.image_name, options.merge(tag_prefix: "base")).cleanup
28
- EcrCleaner.new(builder.image_name, options.merge(tag_prefix: "base")).cleanup
29
- end
30
-
31
- desc "image_name", "displays the full docker image with tag that will be generated"
32
- option :generate, type: :boolean, default: false, desc: "Generate a name without storing it"
33
- long_desc CLI::Help.docker_full_image_name
34
- def image_name
35
- full_image_name = DockerBuilder.new(options).full_image_name
36
- puts full_image_name
37
- end
38
-
39
- desc "cleanup IMAGE_NAME", "Cleans up old images. Keeps a specified amount."
40
- option :keep, type: :numeric, default: 3
41
- option :tag_prefix, default: "ufo"
42
- long_desc CLI::Help.docker_cleanup
43
- def cleanup(image_name)
44
- DockerCleaner.new(image_name, options).cleanup
45
- end
46
- end
47
-
48
- class Tasks < Command
49
- desc "build", "builds task definitions"
50
- long_desc CLI::Help.tasks_build
51
- option :pretty, type: :boolean, default: true, desc: "Pretty format the json for the task definitions"
52
- def build
53
- TasksBuilder.new(options).build
54
- end
55
-
56
- desc "register", "register all built task definitions in ufo/output"
57
- long_desc CLI::Help.tasks_register
58
- def register
59
- TasksRegister.register(:all, options)
60
- end
61
- end
62
-
63
6
  class CLI < Command
64
7
  class_option :verbose, type: :boolean
65
8
  class_option :mute, type: :boolean
@@ -67,11 +10,11 @@ module Ufo
67
10
  class_option :project_root, type: :string, default: '.'
68
11
  class_option :cluster, desc: "Cluster. Overrides ufo/settings.yml."
69
12
 
70
- desc "docker ACTION", "docker related tasks"
13
+ desc "docker [ACTION]", "docker related tasks"
71
14
  long_desc Help.docker
72
15
  subcommand "docker", Docker
73
16
 
74
- desc "tasks ACTION", "task definition related tasks"
17
+ desc "tasks [ACTION]", "task definition related tasks"
75
18
  long_desc Help.tasks
76
19
  subcommand "tasks", Tasks
77
20
 
@@ -84,30 +27,48 @@ module Ufo
84
27
  Init.new(options).setup
85
28
  end
86
29
 
87
- desc "ship [SERVICE]", "ships container to the ECS service"
88
- option :task, desc: "ECS task name, to override the task name convention."
89
- option :target_group, desc: "ELB Target Group ARN."
90
- option :elb, desc: "ELB Name associated with the target_group. Assumes first "
91
- option :elb_prompt, type: :boolean, desc: "Enable ELB prompt", default: true
92
- option :docker, type: :boolean, desc: "Enable docker build and push", default: true
93
- option :wait, type: :boolean, desc: "Wait for deployment to complete", default: false
94
- option :pretty, type: :boolean, default: true, desc: "Pretty format the json for the task definitions"
95
- option :stop_old_tasks, type: :boolean, default: false, desc: "Stop old tasks after waiting for deploying to complete"
96
- option :ecr_keep, type: :numeric, desc: "ECR specific cleanup of old images. Specifies how many images to keep. Only runs if the images are ECR images. Defaults to keeping all the images."
30
+ # common options to ship and ships command
31
+ ship_options = Proc.new do
32
+ option :task, desc: "ECS task name, to override the task name convention."
33
+ option :target_group, desc: "ELB Target Group ARN."
34
+ option :elb, desc: "ELB Name associated with the target_group. Assumes first "
35
+ option :elb_prompt, type: :boolean, desc: "Enable ELB prompt", default: true
36
+ option :docker, type: :boolean, desc: "Enable docker build and push", default: true
37
+ option :wait, type: :boolean, desc: "Wait for deployment to complete", default: false
38
+ option :pretty, type: :boolean, default: true, desc: "Pretty format the json for the task definitions"
39
+ option :stop_old_tasks, type: :boolean, default: false, desc: "Stop old tasks after waiting for deploying to complete"
40
+ option :ecr_keep, type: :numeric, desc: "ECR specific cleanup of old images. Specifies how many images to keep. Only runs if the images are ECR images. Defaults to keeping all the images."
41
+ end
42
+
43
+ desc "ship [SERVICE]", "builds and ships container image to the ECS service"
97
44
  long_desc Help.ship
45
+ ship_options.call
98
46
  def ship(service)
99
- builder = build_docker(options)
100
- task_definition = options[:task] || service # convention
101
- register_task(task_definition, options)
102
- return if ENV['TEST'] # allows quick testing of the ship CLI portion only
47
+ builder = build_docker
103
48
 
49
+ task_definition = options[:task] || service # convention
50
+ Tasks::Builder.register(task_definition, options)
104
51
  ship = Ship.new(service, task_definition, options)
105
52
  ship.deploy
106
- if options[:docker]
107
- DockerCleaner.new(builder.image_name, options).cleanup
108
- EcrCleaner.new(builder.image_name, options).cleanup
53
+
54
+ cleanup(builder.image_name)
55
+ end
56
+
57
+ desc "ships [LIST-OF-SERVICES]", "builds and ships same container image to multiple ECS services"
58
+ long_desc Help.ships
59
+ ship_options.call
60
+ def ships(*services)
61
+ builder = build_docker
62
+
63
+ services.each_with_index do |service|
64
+ service_name, task_defintion_name = service.split(':')
65
+ task_definition = task_defintion_name || service_name # convention
66
+ Tasks::Builder.register(task_definition, options)
67
+ ship = Ship.new(service, task_definition, options)
68
+ ship.deploy
109
69
  end
110
- puts "Docker image shipped: #{builder.full_image_name.green}"
70
+
71
+ cleanup(builder.image_name)
111
72
  end
112
73
 
113
74
  desc "task [TASK_DEFINITION]", "runs a one time task"
@@ -115,14 +76,14 @@ module Ufo
115
76
  option :docker, type: :boolean, desc: "Enable docker build and push", default: true
116
77
  option :command, type: :array, desc: "Override the command used for the container"
117
78
  def task(task_definition)
118
- build_docker(options)
119
- register_task(task_definition, options)
79
+ Docker::Builder.build(options)
80
+ Tasks::Builder.register(task_definition, options)
120
81
  Task.new(task_definition, options).run
121
82
  end
122
83
 
123
84
  desc "destroy [SERVICE]", "destroys the ECS service"
124
85
  long_desc Help.destroy
125
- option :force, type: :boolean, desc: "By pass are you sure prompt."
86
+ option :sure, type: :boolean, desc: "By pass are you sure prompt."
126
87
  def destroy(service)
127
88
  task_definition = options[:task] || service # convention
128
89
  Destroy.new(service, options).bye
@@ -140,8 +101,8 @@ module Ufo
140
101
  end
141
102
 
142
103
  no_tasks do
143
- def build_docker(options)
144
- builder = DockerBuilder.new(options) # outside if because it need docker.full_image_name
104
+ def build_docker
105
+ builder = Docker::Builder.new(options)
145
106
  if options[:docker]
146
107
  builder.build
147
108
  builder.push
@@ -149,14 +110,11 @@ module Ufo
149
110
  builder
150
111
  end
151
112
 
152
- def register_task(task_definition, options)
153
- # task definition and deploy logic are coupled in the Ship class.
154
- # Example: We need to know if the task defintion is a web service to see if we need to
155
- # add the elb target group. The web service information is in the TasksBuilder
156
- # and the elb target group gets set in the Ship class.
157
- # So we always call these together.
158
- TasksBuilder.new(options).build
159
- TasksRegister.register(task_definition, options)
113
+ def cleanup(image_name)
114
+ return unless options[:docker]
115
+
116
+ Docker::Cleaner.new(image_name, options).cleanup
117
+ Ecr::Cleaner.new(image_name, options).cleanup
160
118
  end
161
119
  end
162
120
  end
data/lib/ufo/cli/help.rb CHANGED
@@ -22,66 +22,6 @@ $ ufo docker tag
22
22
  EOL
23
23
  end
24
24
 
25
- def docker_base
26
- <<-EOL
27
-
28
- The docker cache task builds a docker image using the Dockerfile.base file and
29
- updates the FROM Dockerfile image with the generated image from Dockerfile.base.
30
-
31
- Examples:
32
-
33
- $ ufo docker base
34
-
35
- $ ufo docker base --no-push # do not push the image to the registry
36
-
37
- Docker image tongueroo/hi:base-2016-10-21T15-50-57-88071f5 built.
38
- EOL
39
- end
40
-
41
- def docker_build
42
- <<-EOL
43
- Examples:
44
-
45
- $ ufo docker build
46
-
47
- $ ufo docker build --push # also pushes the image to the docker registry
48
-
49
- Docker image tongueroo/hi:ufo-2016-10-21T15-50-57-88071f5 built.
50
- EOL
51
- end
52
-
53
- def docker_full_image_name
54
- <<-EOL
55
- Examples:
56
-
57
- $ ufo docker full_image_name
58
-
59
- Docker image name that will be used: tongueroo/hi:ufo-2016-10-15T19-29-06-88071f5
60
- EOL
61
- end
62
-
63
- def docker_cleanup
64
- <<-EOL
65
- Examples:
66
-
67
- Say you currently have these images:
68
-
69
- * tongueroo/hi:ufo-2016-10-15T19-29-06-88071f5
70
-
71
- * tongueroo/hi:ufo-2016-10-16T19-29-06-88071f5
72
-
73
- * tongueroo/hi:ufo-2016-10-17T19-29-06-88071f5
74
-
75
- * tongueroo/hi:ufo-2016-10-18T19-29-06-88071f5
76
-
77
- To clean them up and keep the 3 more recent:
78
-
79
- $ ufo docker cleanup tongueroo/hi
80
-
81
- This will remove tongueroo/hi:ufo-2016-10-15T19-29-06-88071f5.
82
- EOL
83
- end
84
-
85
25
  def tasks
86
26
  <<-EOL
87
27
  Examples:
@@ -94,46 +34,49 @@ Note all the existing ufo/output generated task defintions are wiped out.
94
34
  EOL
95
35
  end
96
36
 
97
- def tasks_build
37
+ def ship
98
38
  <<-EOL
99
39
  Examples:
100
40
 
101
- $ ufo tasks build
41
+ To build the docker image, generate the task definition and ship it, run:
102
42
 
103
- Builds all the task defintiions.
43
+ $ ufo ship hi-web-prod
104
44
 
105
- Note all the existing ufo/output generated task defintions are wiped out.
106
- EOL
107
- end
45
+ By convention the task and service names match. If you need override to this convention then you can specific the task. For example if you want to ship to the `hi-web-prod-1` service and use the `hi-web-prod` task, run:
108
46
 
109
- def tasks_register
110
- <<-EOL
111
- Examples:
47
+ $ ufo ship hi-web-prod-1 --task hi-web-prod
112
48
 
113
- $ ufo tasks register
114
- All the task defintiions in ufo/output registered.
49
+ The deploy will also created the ECS service if the service does not yet exist on the cluster. The deploy will prompt you for the ELB `--target-group` if you are shipping a web container that does not yet exist. If it is not a web container the `--target-group` option gets ignored.
50
+
51
+ The prommpt can be bypassed by specifying a valid `--target-group` option or specifying the `---no-elb-prompt` option.
52
+
53
+ $ ufo ship hi-web-prod --target-group arn:aws:elasticloadbalancing:us-east-1:123456789:targetgroup/hi-web-prod/jsdlfjsdkd
54
+
55
+ $ ufo ship hi-web-prod --no-elb-prompt
115
56
  EOL
116
57
  end
117
58
 
118
- def ship
59
+ def ships
119
60
  <<-EOL
61
+ Builds docker image, registers it and ships it to multiple services. This deploys the same docker image to multiple ECS services.
62
+
120
63
  Examples:
121
64
 
122
- To build the docker image, generate the task definition and ship it, run:
65
+ $ ufo ships hi-web-prod hi-clock-prod hi-worker-prod
123
66
 
124
- $ ufo ship hi-web-prod
67
+ By convention the task definition and service names match for each of the services you ship. If you need to override to this convention then you can specify the task definition for each service with a special syntax. In the special syntax the service and task definition is separated by a colon. Example:
125
68
 
126
- By convention the task and service names match. If you need override to this convention then you can specific the task. For example if you want to ship to the `hi-web-prod-1` service and use the `hi-web-prod` task, run:
69
+ $ ufo ships hi-web-prod-1:hi-web-prod hi-clock-prod-1 hi-worker-prod-1
127
70
 
128
- $ ufo ship hi-web-prod-1 --task hi-web-prod
71
+ Here ufo will deploy to the hi-web-prod-1 ECS Service using the hi-web-prod task definition, but use the convention for the rest of the service.
129
72
 
130
- The deploy will also created the ECS service if the service does not yet exist on the cluster. The deploy will prompt you for the ELB `--target-group` if you are shipping a web container that does not yet exist. If it is not a web container the `--target-group` option gets ignored.
73
+ For each service being deployed to, ufo will create the ECS service if the service does not yet exist on the cluster. The deploy process will prompt you for the ELB `--target-group` if you are deploying to a 'web' service that does not yet exist. Ufo determines that it is a web service by the name of the service. If the service has 'web' in the name then it is considered a web service. If it is not a web service then the `--target-group` option gets ignored.
131
74
 
132
- The prommpt can be bypassed by specifying a valid `--target-group` option or specifying the `---no-elb-prompt` option.
75
+ The prommpt can be bypassed by specifying a valid `--target-group` option or specifying the `---no-elb-prompt` option. Examples:
133
76
 
134
- $ ufo ship hi-web-prod --target-group arn:aws:elasticloadbalancing:us-east-1:123456789:targetgroup/hi-web-prod/jsdlfjsdkd
77
+ $ ufo ships hi-web-prod hi-clock-prod hi-worker-prod --target-group arn:aws:elasticloadbalancing:us-east-1:123456789:targetgroup/hi-web-prod/jsdlfjsdkd
135
78
 
136
- $ ufo ship hi-web-prod --no-elb-prompt
79
+ $ ufo ships hi-web-prod hi-clock-prod hi-worker-prod --no-elb-prompt
137
80
  EOL
138
81
  end
139
82
 
data/lib/ufo/destroy.rb CHANGED
@@ -50,7 +50,7 @@ module Ufo
50
50
  end
51
51
 
52
52
  def are_you_sure?
53
- return true if @options[:force]
53
+ return true if @options[:sure]
54
54
  puts "You are about to destroy #{@service} service on #{@cluster} cluster."
55
55
  print "Are you sure you want to do this? (y/n) "
56
56
  answer = $stdin.gets.strip
data/lib/ufo/docker.rb ADDED
@@ -0,0 +1,48 @@
1
+ module Ufo
2
+ class Docker < Command
3
+ autoload :Help, 'ufo/docker/help'
4
+ autoload :Builder, 'ufo/docker/builder'
5
+ autoload :Dockerfile, 'ufo/docker/dockerfile'
6
+ autoload :Cleaner, 'ufo/docker/cleaner'
7
+
8
+ desc "build", "builds docker image"
9
+ long_desc Help.build
10
+ option :push, type: :boolean, default: false
11
+ def build
12
+ builder = Docker::Builder.new(options)
13
+ builder.build
14
+ builder.push if options[:push]
15
+ end
16
+
17
+ desc "base", "builds docker image from Dockerfile.base and update current Dockerfile"
18
+ long_desc Help.base
19
+ option :push, type: :boolean, default: true
20
+ def base
21
+ builder = Docker::Builder.new(options.dup.merge(
22
+ image_namespace: "base",
23
+ dockerfile: "Dockerfile.base"
24
+ ))
25
+ builder.build
26
+ builder.push if options[:push]
27
+ builder.update_dockerfile
28
+ Docker::Cleaner.new(builder.image_name, options.merge(tag_prefix: "base")).cleanup
29
+ Ecr::Cleaner.new(builder.image_name, options.merge(tag_prefix: "base")).cleanup
30
+ end
31
+
32
+ desc "image_name", "displays the full docker image with tag that will be generated"
33
+ option :generate, type: :boolean, default: false, desc: "Generate a name without storing it"
34
+ long_desc Help.full_image_name
35
+ def image_name
36
+ full_image_name = Docker::Builder.new(options).full_image_name
37
+ puts full_image_name
38
+ end
39
+
40
+ desc "cleanup IMAGE_NAME", "Cleans up old images. Keeps a specified amount."
41
+ option :keep, type: :numeric, default: 3
42
+ option :tag_prefix, default: "ufo"
43
+ long_desc Help.cleanup
44
+ def cleanup(image_name)
45
+ Docker::Cleaner.new(image_name, options).cleanup
46
+ end
47
+ end
48
+ end
@@ -1,7 +1,15 @@
1
1
  module Ufo
2
- class DockerBuilder
3
- include PrettyTime
4
- include Execute
2
+ class Docker::Builder
3
+ include Util
4
+
5
+ def self.build(options)
6
+ builder = Docker::Builder.new(options) # outside if because it need builder.full_image_name
7
+ if options[:docker]
8
+ builder.build
9
+ builder.push
10
+ end
11
+ builder
12
+ end
5
13
 
6
14
  def initialize(options={})
7
15
  @options = options
@@ -54,7 +62,7 @@ module Ufo
54
62
  def update_auth_token
55
63
  return unless ecr_image?
56
64
  repo_domain = "https://#{image_name.split('/').first}"
57
- auth = EcrAuth.new(repo_domain)
65
+ auth = Ecr::Auth.new(repo_domain)
58
66
  auth.update
59
67
  end
60
68
 
@@ -117,8 +125,8 @@ module Ufo
117
125
  end
118
126
 
119
127
  def update_dockerfile
120
- updater = DockerfileUpdater.new(full_image_name, @options)
121
- updater.update
128
+ dockerfile = Docker::Dockerfile.new(full_image_name, @options)
129
+ dockerfile.update
122
130
  end
123
131
 
124
132
  def say(msg)
@@ -1,6 +1,6 @@
1
1
  module Ufo
2
- class DockerCleaner
3
- include Execute
2
+ class Docker::Cleaner
3
+ include Util
4
4
 
5
5
  def initialize(docker_image_name, options)
6
6
  # docker_image_name does not containg the tag
@@ -1,5 +1,5 @@
1
1
  module Ufo
2
- class DockerfileUpdater
2
+ class Docker::Dockerfile
3
3
  def initialize(full_image_name, options={})
4
4
  @full_image_name = full_image_name
5
5
  @options = options
@@ -0,0 +1,65 @@
1
+ module Ufo
2
+ module Docker::Help
3
+ def base
4
+ <<-EOL
5
+
6
+ The docker cache task builds a docker image using the Dockerfile.base file and
7
+ updates the FROM Dockerfile image with the generated image from Dockerfile.base.
8
+
9
+ Examples:
10
+
11
+ $ ufo docker base
12
+
13
+ $ ufo docker base --no-push # do not push the image to the registry
14
+
15
+ Docker image tongueroo/hi:base-2016-10-21T15-50-57-88071f5 built.
16
+ EOL
17
+ end
18
+
19
+ def build
20
+ <<-EOL
21
+ Examples:
22
+
23
+ $ ufo docker build
24
+
25
+ $ ufo docker build --push # also pushes the image to the docker registry
26
+
27
+ Docker image tongueroo/hi:ufo-2016-10-21T15-50-57-88071f5 built.
28
+ EOL
29
+ end
30
+
31
+ def full_image_name
32
+ <<-EOL
33
+ Examples:
34
+
35
+ $ ufo docker full_image_name
36
+
37
+ Docker image name that will be used: tongueroo/hi:ufo-2016-10-15T19-29-06-88071f5
38
+ EOL
39
+ end
40
+
41
+ def cleanup
42
+ <<-EOL
43
+ Examples:
44
+
45
+ Say you currently have these images:
46
+
47
+ * tongueroo/hi:ufo-2016-10-15T19-29-06-88071f5
48
+
49
+ * tongueroo/hi:ufo-2016-10-16T19-29-06-88071f5
50
+
51
+ * tongueroo/hi:ufo-2016-10-17T19-29-06-88071f5
52
+
53
+ * tongueroo/hi:ufo-2016-10-18T19-29-06-88071f5
54
+
55
+ To clean them up and keep the 3 more recent:
56
+
57
+ $ ufo docker cleanup tongueroo/hi
58
+
59
+ This will remove tongueroo/hi:ufo-2016-10-15T19-29-06-88071f5.
60
+ EOL
61
+ end
62
+
63
+ extend self
64
+ end
65
+ end
data/lib/ufo/dsl.rb CHANGED
@@ -25,7 +25,37 @@ module Ufo
25
25
  # of this class.
26
26
  def evaluate_template_definitions
27
27
  source_code = IO.read(@template_definitions_path)
28
- instance_eval(source_code, @template_definitions_path)
28
+ begin
29
+ instance_eval(source_code, @template_definitions_path)
30
+ rescue Exception => e
31
+ task_definition_error(e)
32
+ puts "\nFull error:"
33
+ raise
34
+ end
35
+ end
36
+
37
+ # Prints out a user friendly task_definition error message
38
+ def task_definition_error(e)
39
+ error_info = e.backtrace.first
40
+ path, line_no, _ = error_info.split(':')
41
+ line_no = line_no.to_i
42
+ puts "Error evaluating #{path}:".colorize(:red)
43
+ puts e.message
44
+ puts "Here's the line in #{path} with the error:\n\n"
45
+
46
+ contents = IO.read(path)
47
+ content_lines = contents.split("\n")
48
+ context = 5 # lines of context
49
+ top, bottom = [line_no-context-1, 0].max, line_no+context-1
50
+ spacing = content_lines.size.to_s.size
51
+ content_lines[top..bottom].each_with_index do |line_content, index|
52
+ line_number = top+index+1
53
+ if line_number == line_no
54
+ printf("%#{spacing}d %s\n".colorize(:red), line_number, line_content)
55
+ else
56
+ printf("%#{spacing}d %s\n", line_number, line_content)
57
+ end
58
+ end
29
59
  end
30
60
 
31
61
  def build_task_definitions
@@ -23,7 +23,7 @@ module Ufo
23
23
  end
24
24
 
25
25
  def full_image_name
26
- DockerBuilder.new(@options).full_image_name
26
+ Docker::Builder.new(@options).full_image_name
27
27
  end
28
28
 
29
29
  #############
data/lib/ufo/ecr.rb ADDED
@@ -0,0 +1,6 @@
1
+ module Ufo
2
+ module Ecr
3
+ autoload :Auth, 'ufo/ecr/auth'
4
+ autoload :Cleaner, 'ufo/ecr/cleaner'
5
+ end
6
+ end
@@ -1,5 +1,5 @@
1
1
  module Ufo
2
- class EcrAuth
2
+ class Ecr::Auth
3
3
  include AwsServices
4
4
 
5
5
  def initialize(repo_domain)
@@ -4,7 +4,7 @@ require "json"
4
4
  #
5
5
  # ufo ship app-web --cluster prod --noop
6
6
  module Ufo
7
- class EcrCleaner
7
+ class Ecr::Cleaner
8
8
  include AwsServices
9
9
  include Defaults
10
10
 
@@ -50,7 +50,7 @@ module Ufo
50
50
 
51
51
  def update_auth_token
52
52
  repo_domain = "https://#{@docker_image_name.split('/').first}"
53
- auth = EcrAuth.new(repo_domain)
53
+ auth = Ecr::Auth.new(repo_domain)
54
54
  auth.update
55
55
  end
56
56
 
data/lib/ufo/init.rb CHANGED
@@ -21,7 +21,7 @@ module Ufo
21
21
  dest = src.gsub(%r{.*starter_project/},'')
22
22
  dest = "#{@project_root}/#{dest}"
23
23
 
24
- if File.exist?(dest) and !@options[:force]
24
+ if File.exist?(dest) and !@options[:sure]
25
25
  puts "exists: #{dest}".yellow unless @options[:quiet]
26
26
  else
27
27
  dirname = File.dirname(dest)
data/lib/ufo/ship.rb CHANGED
@@ -24,9 +24,8 @@ module Ufo
24
24
  class Ship
25
25
  include Defaults
26
26
  include AwsServices
27
- include PrettyTime
27
+ include Util
28
28
 
29
- # service can be a pattern
30
29
  def initialize(service, task_definition, options={})
31
30
  @service = service
32
31
  @task_definition = task_definition
@@ -51,7 +50,15 @@ module Ufo
51
50
  # Example:
52
51
  # No way to map: hi-.*-prod -> hi-web-prod hi-worker-prod hi-clock-prod
53
52
  def deploy
54
- puts "Shipping #{@service}...".green unless @options[:mute]
53
+ message = "Shipping #{@service}..."
54
+ unless @options[:mute]
55
+ if @options[:noop]
56
+ puts "NOOP: #{message}"
57
+ return
58
+ else
59
+ puts message.green
60
+ end
61
+ end
55
62
 
56
63
  ensure_cluster_exist
57
64
  process_single_service
@@ -333,8 +340,8 @@ module Ufo
333
340
  task_definition_path = "ufo/output/#{task_definition}.json"
334
341
  task_definition_full_path = "#{@project_root}/#{task_definition_path}"
335
342
  unless File.exist?(task_definition_full_path)
336
- puts "ERROR: Unable to find the task definition at #{task_definition_path}."
337
- puts "Are you sure you have defined it in ufo/template_definitions.rb?"
343
+ puts "ERROR: Unable to find the task definition at #{task_definition_path}.".colorize(:red)
344
+ puts "Are you sure you have defined it in ufo/template_definitions.rb?".colorize(:red)
338
345
  exit
339
346
  end
340
347
  task_definition = JSON.load(IO.read(task_definition_full_path))
data/lib/ufo/tasks.rb ADDED
@@ -0,0 +1,20 @@
1
+ module Ufo
2
+ class Tasks < Command
3
+ autoload :Help, 'ufo/tasks/help'
4
+ autoload :Builder, 'ufo/tasks/builder'
5
+ autoload :Register, 'ufo/tasks/register'
6
+
7
+ desc "build", "builds task definitions"
8
+ long_desc Help.build
9
+ option :pretty, type: :boolean, default: true, desc: "Pretty format the json for the task definitions"
10
+ def build
11
+ Tasks::Builder.new(options).build
12
+ end
13
+
14
+ desc "register", "register all built task definitions in ufo/output"
15
+ long_desc Help.register
16
+ def register
17
+ Tasks::Register.register(:all, options)
18
+ end
19
+ end
20
+ end
@@ -1,7 +1,16 @@
1
1
  module Ufo
2
- autoload :DSL, 'ufo/dsl'
2
+ class Tasks::Builder
3
+ # build and registers together
4
+ def self.register(task_definition, options)
5
+ # task definition and deploy logic are coupled in the Ship class.
6
+ # Example: We need to know if the task defintion is a web service to see if we need to
7
+ # add the elb target group. The web service information is in the Tasks::Builder
8
+ # and the elb target group gets set in the Ship class.
9
+ # So we always call these together.
10
+ Tasks::Builder.new(options).build
11
+ Tasks::Register.register(task_definition, options)
12
+ end
3
13
 
4
- class TasksBuilder
5
14
  def initialize(options={})
6
15
  @options = options
7
16
  @project_root = options[:project_root] || '.'
@@ -0,0 +1,26 @@
1
+ module Ufo
2
+ module Tasks::Help
3
+ def build
4
+ <<-EOL
5
+ Examples:
6
+
7
+ $ ufo tasks build
8
+
9
+ Builds all the task defintiions.
10
+
11
+ Note all the existing ufo/output generated task defintions are wiped out.
12
+ EOL
13
+ end
14
+
15
+ def register
16
+ <<-EOL
17
+ Examples:
18
+
19
+ $ ufo tasks register
20
+ All the task defintiions in ufo/output registered.
21
+ EOL
22
+ end
23
+
24
+ extend self
25
+ end
26
+ end
@@ -2,14 +2,14 @@ require 'plissken' # Hash#to_snake_keys
2
2
  require 'json'
3
3
 
4
4
  module Ufo
5
- class TasksRegister
5
+ class Tasks::Register
6
6
  include AwsServices
7
7
 
8
8
  def self.register(task_name, options={})
9
9
  project_root = options[:project_root] || '.'
10
10
  Dir.glob("#{project_root}/ufo/output/*").each do |path|
11
11
  if task_name == :all or path.include?(task_name)
12
- task_register = TasksRegister.new(path, options)
12
+ task_register = Tasks::Register.new(path, options)
13
13
  task_register.register
14
14
  end
15
15
  end
@@ -1,5 +1,5 @@
1
1
  module Ufo
2
- module Execute
2
+ module Util
3
3
  def execute(command, local_options={})
4
4
  command = "cd #{@project_root} && #{command}"
5
5
  # local_options[:live] overrides the global @options[:noop]
@@ -15,5 +15,16 @@ module Ufo
15
15
  end
16
16
  result
17
17
  end
18
+
19
+ # http://stackoverflow.com/questions/4175733/convert-duration-to-hoursminutesseconds-or-similar-in-rails-3-or-ruby
20
+ def pretty_time(total_seconds)
21
+ minutes = (total_seconds / 60) % 60
22
+ seconds = total_seconds % 60
23
+ if total_seconds < 60
24
+ "#{seconds.to_i}s"
25
+ else
26
+ "#{minutes.to_i}m #{seconds.to_i}s"
27
+ end
28
+ end
18
29
  end
19
30
  end
data/lib/ufo/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Ufo
2
- VERSION = "1.2.0"
2
+ VERSION = "1.5.0"
3
3
  end
@@ -0,0 +1,3 @@
1
+ [default]
2
+ region = us-west-2
3
+
data/spec/lib/cli_spec.rb CHANGED
@@ -47,6 +47,16 @@ describe Ufo::CLI do
47
47
  end
48
48
  end
49
49
 
50
+ context "ships" do
51
+ it "deploys software to multiple services" do
52
+ out = execute("bin/ufo ships hi-web-prod hi-worker-prod #{@args} --no-wait")
53
+ # cannot look for Software shipped! because
54
+ # ship.deploy unless ENV['TEST'] # to allow me to quickly test CLI portion only
55
+ # just testing the CLI portion. The ship class itself is tested via ship_spec.rb
56
+ expect(out).to include("Task Definitions built")
57
+ end
58
+ end
59
+
50
60
  context "task" do
51
61
  it "runs one time task" do
52
62
  out = execute("bin/ufo task hi-migrate-prod #{@args}")
@@ -1,8 +1,8 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Ufo::EcrAuth do
3
+ describe Ufo::Ecr::Auth do
4
4
  let(:repo_domain) { "https://123456789.dkr.ecr.us-east-1.amazonaws.com" }
5
- let(:auth) { Ufo::EcrAuth.new(repo_domain) }
5
+ let(:auth) { Ufo::Ecr::Auth.new(repo_domain) }
6
6
  before(:each) do
7
7
  allow(auth).to receive(:fetch_auth_token).and_return("opensesame")
8
8
  end
@@ -10,7 +10,6 @@ describe Ufo::EcrAuth do
10
10
  context("update") do
11
11
  before(:each) do
12
12
  clean_home
13
- ENV['HOME'] = "spec/fixtures/home"
14
13
  end
15
14
 
16
15
  context("missing ~/.docker/config.json") do
@@ -24,7 +23,6 @@ describe Ufo::EcrAuth do
24
23
 
25
24
  context("existing ~/.docker/config.json") do
26
25
  it "should update the auth token" do
27
- FileUtils.cp_r("spec/fixtures/home_existing", "spec/fixtures/home")
28
26
  auth.update
29
27
  data = JSON.load(IO.read("spec/fixtures/home/.docker/config.json"))
30
28
  auth_token = data["auths"][repo_domain]["auth"]
@@ -35,5 +33,6 @@ describe Ufo::EcrAuth do
35
33
 
36
34
  def clean_home
37
35
  FileUtils.rm_rf("spec/fixtures/home")
36
+ FileUtils.cp_r("spec/fixtures/home_existing", "spec/fixtures/home")
38
37
  end
39
38
  end
@@ -1,10 +1,10 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Ufo::EcrCleaner do
3
+ describe Ufo::Ecr::Cleaner do
4
4
  let(:docker_image_name) { "123456789.dkr.ecr.us-east-1.amazonaws.com/my-name" }
5
5
  let(:repo_domain) { "https://123456789.dkr.ecr.us-east-1.amazonaws.com" }
6
6
  let(:cleaner) do
7
- Ufo::EcrCleaner.new(docker_image_name,
7
+ Ufo::Ecr::Cleaner.new(docker_image_name,
8
8
  project_root: "spec/fixtures/hi",
9
9
  ecr_keep: 3, # using 3 to test, default is 30
10
10
  mute: true
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ufo
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tung Nguyen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-06-01 00:00:00.000000000 Z
11
+ date: 2017-06-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -181,26 +181,31 @@ files:
181
181
  - lib/ufo/command.rb
182
182
  - lib/ufo/defaults.rb
183
183
  - lib/ufo/destroy.rb
184
- - lib/ufo/docker_builder.rb
185
- - lib/ufo/docker_cleaner.rb
186
- - lib/ufo/dockerfile_updater.rb
184
+ - lib/ufo/docker.rb
185
+ - lib/ufo/docker/builder.rb
186
+ - lib/ufo/docker/cleaner.rb
187
+ - lib/ufo/docker/dockerfile.rb
188
+ - lib/ufo/docker/help.rb
187
189
  - lib/ufo/dsl.rb
188
190
  - lib/ufo/dsl/helper.rb
189
191
  - lib/ufo/dsl/outputter.rb
190
192
  - lib/ufo/dsl/task_definition.rb
191
- - lib/ufo/ecr_auth.rb
192
- - lib/ufo/ecr_cleaner.rb
193
- - lib/ufo/execute.rb
193
+ - lib/ufo/ecr.rb
194
+ - lib/ufo/ecr/auth.rb
195
+ - lib/ufo/ecr/cleaner.rb
194
196
  - lib/ufo/init.rb
195
- - lib/ufo/pretty_time.rb
196
197
  - lib/ufo/scale.rb
197
198
  - lib/ufo/settings.rb
198
199
  - lib/ufo/ship.rb
199
200
  - lib/ufo/task.rb
200
- - lib/ufo/tasks_builder.rb
201
- - lib/ufo/tasks_register.rb
201
+ - lib/ufo/tasks.rb
202
+ - lib/ufo/tasks/builder.rb
203
+ - lib/ufo/tasks/help.rb
204
+ - lib/ufo/tasks/register.rb
202
205
  - lib/ufo/templates/default.json.erb
206
+ - lib/ufo/util.rb
203
207
  - lib/ufo/version.rb
208
+ - spec/fixtures/home_existing/.aws/config
204
209
  - spec/fixtures/home_existing/.docker/config.json
205
210
  - spec/lib/cli_spec.rb
206
211
  - spec/lib/ecr_auth_spec.rb
@@ -234,6 +239,7 @@ signing_key:
234
239
  specification_version: 4
235
240
  summary: Build Docker Containers and Ship Them to AWS ECS
236
241
  test_files:
242
+ - spec/fixtures/home_existing/.aws/config
237
243
  - spec/fixtures/home_existing/.docker/config.json
238
244
  - spec/lib/cli_spec.rb
239
245
  - spec/lib/ecr_auth_spec.rb
@@ -1,14 +0,0 @@
1
- module Ufo
2
- module PrettyTime
3
- # http://stackoverflow.com/questions/4175733/convert-duration-to-hoursminutesseconds-or-similar-in-rails-3-or-ruby
4
- def pretty_time(total_seconds)
5
- minutes = (total_seconds / 60) % 60
6
- seconds = total_seconds % 60
7
- if total_seconds < 60
8
- "#{seconds.to_i}s"
9
- else
10
- "#{minutes.to_i}m #{seconds.to_i}s"
11
- end
12
- end
13
- end
14
- end