ufo 0.0.6

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 (50) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.rspec +3 -0
  4. data/CHANGELOG.md +12 -0
  5. data/Gemfile +6 -0
  6. data/Gemfile.lock +128 -0
  7. data/Guardfile +12 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +227 -0
  10. data/Rakefile +6 -0
  11. data/bin/ufo +10 -0
  12. data/lib/starter_project/.env.prod +3 -0
  13. data/lib/starter_project/Dockerfile +20 -0
  14. data/lib/starter_project/bin/deploy +12 -0
  15. data/lib/starter_project/ufo/settings.yml +7 -0
  16. data/lib/starter_project/ufo/task_definitions.rb +44 -0
  17. data/lib/starter_project/ufo/templates/main.json.erb +39 -0
  18. data/lib/ufo.rb +26 -0
  19. data/lib/ufo/aws_services.rb +21 -0
  20. data/lib/ufo/cli.rb +136 -0
  21. data/lib/ufo/cli/help.rb +164 -0
  22. data/lib/ufo/defaults.rb +46 -0
  23. data/lib/ufo/destroy.rb +60 -0
  24. data/lib/ufo/docker_builder.rb +120 -0
  25. data/lib/ufo/docker_cleaner.rb +52 -0
  26. data/lib/ufo/dockerfile_updater.rb +42 -0
  27. data/lib/ufo/dsl.rb +93 -0
  28. data/lib/ufo/dsl/helper.rb +47 -0
  29. data/lib/ufo/dsl/outputter.rb +40 -0
  30. data/lib/ufo/dsl/task_definition.rb +65 -0
  31. data/lib/ufo/ecr_auth.rb +35 -0
  32. data/lib/ufo/ecr_cleaner.rb +66 -0
  33. data/lib/ufo/execute.rb +19 -0
  34. data/lib/ufo/init.rb +83 -0
  35. data/lib/ufo/pretty_time.rb +14 -0
  36. data/lib/ufo/scale.rb +34 -0
  37. data/lib/ufo/settings.rb +33 -0
  38. data/lib/ufo/ship.rb +436 -0
  39. data/lib/ufo/tasks_builder.rb +30 -0
  40. data/lib/ufo/tasks_register.rb +51 -0
  41. data/lib/ufo/templates/default.json.erb +39 -0
  42. data/lib/ufo/version.rb +3 -0
  43. data/spec/fixtures/home_existing/.docker/config.json +10 -0
  44. data/spec/lib/cli_spec.rb +50 -0
  45. data/spec/lib/ecr_auth_spec.rb +39 -0
  46. data/spec/lib/ecr_cleaner_spec.rb +32 -0
  47. data/spec/lib/ship_spec.rb +77 -0
  48. data/spec/spec_helper.rb +28 -0
  49. data/ufo.gemspec +34 -0
  50. metadata +267 -0
@@ -0,0 +1,3 @@
1
+ # fine to have comment in this file
2
+ ADMIN_PASSWORD=secret
3
+ DATABASE_URL=mysql2://user:pass@domain.com:3306/dbname # comment can be at the end too
@@ -0,0 +1,20 @@
1
+ FROM ruby:2.3.3
2
+ MAINTAINER Tung Nguyen <tongueroo@gmail.com>
3
+
4
+ # This is just a sample Dockerfile. This is meant to be overriden.
5
+
6
+ # Install bundle of gems first in a layer
7
+ # so if the Gemfile doesnt chagne it wont have to install gems again
8
+ WORKDIR /tmp
9
+ COPY Gemfile* /tmp/
10
+ RUN bundle install && rm -rf /root/.bundle/cache
11
+
12
+ # Add the Rails app
13
+ ENV HOME /root
14
+ WORKDIR /app
15
+ COPY . /app
16
+ RUN bundle install
17
+ RUN mkdir -p tmp/cache tmp/pids
18
+
19
+ EXPOSE 5001
20
+ CMD ["bin/web"]
@@ -0,0 +1,12 @@
1
+ #!/bin/bash -xe
2
+
3
+ # Generated started bin/deploy script. Meant to be example only and should probably
4
+ # be overridden.
5
+
6
+ # Only build docker image on with the first command.
7
+ ufo ship <%= @app %>-clock --cluster <%= @cluster %> --no-wait
8
+ # Skipping the docker build phase for the rest of the ship commands
9
+ ufo ship <%= @app %>-worker --cluster <%= @cluster %> --no-wait --no-docker
10
+ # The final service will wait for the deploy to finish. Please specify an
11
+ # Application Load Balancer if creating the service for the first time.
12
+ ufo ship <%= @app %>-web --cluster <%= @cluster %> --no-docker --target-group ''
@@ -0,0 +1,7 @@
1
+ image: <%= @image %>
2
+ service_cluster:
3
+ default: <%= @cluster %> # default cluster
4
+ # can override the default cluster for each service. CLI overrides all of these settings.
5
+ <%= @app %>-web-prod:
6
+ <%= @app %>-clock-prod:
7
+ <%= @app %>-worker-prod:
@@ -0,0 +1,44 @@
1
+ # There will be some special variables that are automatically available in this file.
2
+ #
3
+ # Some of variables are from the Dockerfile and some are from other places.
4
+ #
5
+ # * helper.full_image_name - Docker image name with the tag when docker image is built by ufo. This is defined in ufo/settings.yml. The helper.full_image_name includes the git sha tongueroo/hi:ufo-[sha].
6
+ # * helper.dockerfile_port - Expose port in the Dockerfile. Only supports one exposed port, the first one that is encountered.
7
+ #
8
+ # env_vars - is a helper method that generates the proper environment Array of Hashes
9
+
10
+ # common variables
11
+ common = {
12
+ image: helper.full_image_name, # includes the git sha tongueroo/hi:ufo-[sha].
13
+ cpu: 128,
14
+ memory_reservation: 256,
15
+ environment: env_file(".env.prod")
16
+ }
17
+
18
+ task_definition "<%= @app %>-web" do
19
+ source "main" # will use ufo/templates/main.json.erb
20
+ variables(common.dup.deep_merge(
21
+ family: task_definition_name,
22
+ name: "web",
23
+ container_port: helper.dockerfile_port,
24
+ command: ["bin/web"]
25
+ ))
26
+ end
27
+
28
+ task_definition "<%= @app %>-worker" do
29
+ source "main" # will use ufo/templates/main.json.erb
30
+ variables(common.dup.deep_merge(
31
+ family: task_definition_name,
32
+ name: "worker",
33
+ command: ["bin/worker"]
34
+ ))
35
+ end
36
+
37
+ task_definition "<%= @app %>-clock" do
38
+ source "main" # will use ufo/templates/main.json.erb
39
+ variables(common.dup.deep_merge(
40
+ family: task_definition_name,
41
+ name: "clock",
42
+ command: ["bin/clock"]
43
+ ))
44
+ end
@@ -0,0 +1,39 @@
1
+ {
2
+ "family": "<%= @family %>",
3
+ "containerDefinitions": [
4
+ {
5
+ "name": "<%= @name %>",
6
+ "image": "<%= @image %>",
7
+ "cpu": <%= @cpu %>,
8
+ <% if @memory %>
9
+ "memory": <%= @memory %>,
10
+ <% end %>
11
+ <% if @memory_reservation %>
12
+ "memoryReservation": <%= @memory_reservation %>,
13
+ <% end %>
14
+ <% if @container_port %>
15
+ "portMappings": [
16
+ {
17
+ "containerPort": "<%= @container_port %>",
18
+ "protocol": "tcp"
19
+ }
20
+ ],
21
+ <% end %>
22
+ "command": <%= @command.to_json %>,
23
+ <% if @environment %>
24
+ "environment": <%= @environment.to_json %>,
25
+ <% end %>
26
+ <% if @awslogs_group %>
27
+ "logConfiguration": {
28
+ "logDriver": "awslogs",
29
+ "options": {
30
+ "awslogs-group": "<%= @awslogs_group %>",
31
+ "awslogs-region": "<%= @awslogs_region || 'us-east-1' %>",
32
+ "awslogs-stream-prefix": "<%= @awslogs_stream_prefix %>"
33
+ }
34
+ },
35
+ <% end %>
36
+ "essential": true
37
+ }
38
+ ]
39
+ }
data/lib/ufo.rb ADDED
@@ -0,0 +1,26 @@
1
+ $:.unshift(File.expand_path("../", __FILE__))
2
+ require "ufo/version"
3
+ require "pp"
4
+ require 'deep_merge'
5
+ require "colorize"
6
+
7
+ module Ufo
8
+ autoload :Settings, 'ufo/settings'
9
+ autoload :PrettyTime, 'ufo/pretty_time'
10
+ autoload :Execute, 'ufo/execute'
11
+ autoload :Init, 'ufo/init'
12
+ autoload :EcrAuth, 'ufo/ecr_auth'
13
+ autoload :CLI, 'ufo/cli'
14
+ autoload :DockerBuilder, 'ufo/docker_builder'
15
+ autoload :DockerfileUpdater, 'ufo/dockerfile_updater'
16
+ autoload :DockerCleaner, 'ufo/docker_cleaner'
17
+ autoload :TasksBuilder, 'ufo/tasks_builder'
18
+ autoload :TasksRegister, 'ufo/tasks_register'
19
+ autoload :Ship, 'ufo/ship'
20
+ autoload :EcrCleaner, 'ufo/ecr_cleaner'
21
+ autoload :Destroy, 'ufo/destroy'
22
+ autoload :Scale, 'ufo/scale'
23
+ # modules
24
+ autoload :Defaults, 'ufo/defaults'
25
+ autoload :AwsServices, 'ufo/aws_services'
26
+ end
@@ -0,0 +1,21 @@
1
+ require 'aws-sdk'
2
+
3
+ module Ufo
4
+ module AwsServices
5
+ def ecs
6
+ @ecs ||= Aws::ECS::Client.new(region: region)
7
+ end
8
+
9
+ def elb
10
+ @elb ||= Aws::ElasticLoadBalancingV2::Client.new(region: region)
11
+ end
12
+
13
+ def ecr
14
+ @ecr ||= Aws::ECR::Client.new(region: region)
15
+ end
16
+
17
+ def region
18
+ ENV['REGION'] || 'us-east-1'
19
+ end
20
+ end
21
+ end
data/lib/ufo/cli.rb ADDED
@@ -0,0 +1,136 @@
1
+ require 'thor'
2
+ require 'ufo/cli/help'
3
+
4
+ module Ufo
5
+ class Docker < Thor
6
+ desc "build", "builds docker image"
7
+ long_desc CLI::Help.docker_build
8
+ option :push, type: :boolean, default: false
9
+ def build
10
+ builder = DockerBuilder.new(options)
11
+ builder.build
12
+ builder.push if options[:push]
13
+ end
14
+
15
+ desc "base", "builds docker image from Dockerfile.base"
16
+ long_desc CLI::Help.docker_base
17
+ option :push, type: :boolean, default: true
18
+ def base
19
+ builder = DockerBuilder.new(options.dup.merge(
20
+ image_namespace: "base",
21
+ dockerfile: "Dockerfile.base"
22
+ ))
23
+ builder.build
24
+ builder.push if options[:push]
25
+ builder.update_dockerfile
26
+ DockerCleaner.new(builder.image_name, options.merge(tag_prefix: "base")).cleanup
27
+ EcrCleaner.new(builder.image_name, options.merge(tag_prefix: "base")).cleanup
28
+ end
29
+
30
+ desc "full_image_name", "displays the full docker image with tag that will be generated"
31
+ long_desc CLI::Help.docker_full_image_name
32
+ def full_image_name
33
+ full_image_name = DockerBuilder.new(options).full_image_name
34
+ puts "Docker image name that will be used: #{full_image_name}"
35
+ end
36
+
37
+ desc "cleanup IMAGE_NAME", "Cleans up old images. Keeps a specified amount."
38
+ option :keep, type: :numeric, default: 3
39
+ option :tag_prefix, default: "ufo"
40
+ long_desc CLI::Help.docker_cleanup
41
+ def cleanup(image_name)
42
+ DockerCleaner.new(image_name, options).cleanup
43
+ end
44
+ end
45
+
46
+ class Tasks < Thor
47
+ desc "build", "builds task definitions"
48
+ long_desc CLI::Help.tasks_build
49
+ option :pretty, type: :boolean, default: true, desc: "Pretty format the json for the task definitions"
50
+ def build
51
+ TasksBuilder.new(options).build
52
+ end
53
+
54
+ desc "register", "register all built task definitions in ufo/output"
55
+ long_desc CLI::Help.tasks_register
56
+ def register
57
+ TasksRegister.register_all(options)
58
+ end
59
+ end
60
+
61
+ class CLI < Thor
62
+ class_option :verbose, type: :boolean
63
+ class_option :mute, type: :boolean
64
+ class_option :noop, type: :boolean
65
+ class_option :project_root, type: :string, default: '.'
66
+ class_option :cluster, desc: "Cluster. Overrides ufo/settings.yml."
67
+
68
+ desc "docker ACTION", "docker related tasks"
69
+ long_desc Help.docker
70
+ subcommand "docker", Docker
71
+
72
+ desc "tasks ACTION", "task definition related tasks"
73
+ long_desc Help.tasks
74
+ subcommand "tasks", Tasks
75
+
76
+ desc "init", "setup initial ufo files"
77
+ option :cluster, type: :string, required: true, desc: "ECS cluster name. Example: default"
78
+ option :image, type: :string, required: true, desc: "Docker image name without the tag. Example: tongueroo/hi"
79
+ option :app, type: :string, required: true, desc: "App name. Preferably one word. Used in the generated ufo/task_definitions.rb."
80
+ long_desc Help.init
81
+ def init
82
+ Init.new(options).setup
83
+ end
84
+
85
+ desc "ship [SERVICE]", "ships container to the ECS service"
86
+ option :task, desc: "ECS task name, to override the task name convention."
87
+ option :target_group, desc: "ELB Target Group ARN."
88
+ option :elb, desc: "ELB Name associated with the target_group. Assumes first "
89
+ option :elb_prompt, type: :boolean, desc: "Enable ELB prompt", default: true
90
+ option :docker, type: :boolean, desc: "Enable docker build and push", default: true
91
+ option :wait, type: :boolean, desc: "Wait for deployment to complete", default: true
92
+ option :pretty, type: :boolean, default: true, desc: "Pretty format the json for the task definitions"
93
+ option :stop_old_tasks, type: :boolean, default: false, desc: "Stop old tasks after waiting for deploying to complete"
94
+ 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."
95
+ long_desc Help.ship
96
+ def ship(service)
97
+ builder = DockerBuilder.new(options) # outside if because it need docker.full_image_name
98
+ if options[:docker]
99
+ builder.build
100
+ builder.push
101
+ end
102
+
103
+ # task definition and deploy logic are coupled in the Ship class.
104
+ # Example: We need to know if the task defintion is a web service to see if we need to
105
+ # add the elb target group. The web service information is in the TasksBuilder
106
+ # and the elb target group gets set in the Ship class.
107
+ # So we always call these together.
108
+ TasksBuilder.new(options).build
109
+ TasksRegister.register(service, options)
110
+ task_definition = options[:task] || service # convention
111
+ ship = Ship.new(service, task_definition, options)
112
+
113
+ return if ENV['TEST'] # to allow me to quickly test most of the ship CLI portion only
114
+ ship.deploy
115
+ if options[:docker]
116
+ DockerCleaner.new(builder.image_name, options).cleanup
117
+ EcrCleaner.new(builder.image_name, options).cleanup
118
+ end
119
+ puts "Docker image shipped: #{builder.full_image_name.green}"
120
+ end
121
+
122
+ desc "destroy [SERVICE]", "destroys the ECS service"
123
+ long_desc Help.destroy
124
+ option :force, type: :boolean, desc: "By pass are you sure prompt."
125
+ def destroy(service)
126
+ task_definition = options[:task] || service # convention
127
+ Destroy.new(service, options).bye
128
+ end
129
+
130
+ desc "scale [SERVICE] [COUNT]", "scale the ECS service"
131
+ long_desc Help.scale
132
+ def scale(service, count)
133
+ Scale.new(service, count, options).update
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,164 @@
1
+ module Ufo
2
+ class CLI < Thor
3
+ class Help
4
+ class << self
5
+ def init
6
+ <<-EOL
7
+ Examples:
8
+
9
+ $ ufo init --cluster prod --image tongueroo/hi --app hi
10
+
11
+ The image should not include the tag since the tag is generated upon a `ufo ship`.
12
+ EOL
13
+ end
14
+
15
+ def docker
16
+ <<-EOL
17
+ Examples:
18
+
19
+ $ ufo docker build
20
+
21
+ $ ufo docker tag
22
+ EOL
23
+ end
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
+ def tasks
86
+ <<-EOL
87
+ Examples:
88
+
89
+ $ ufo tasks build
90
+
91
+ Builds all the task defintiions.
92
+
93
+ Note all the existing ufo/output generated task defintions are wiped out.
94
+ EOL
95
+ end
96
+
97
+ def tasks_build
98
+ <<-EOL
99
+ Examples:
100
+
101
+ $ ufo tasks build
102
+
103
+ Builds all the task defintiions.
104
+
105
+ Note all the existing ufo/output generated task defintions are wiped out.
106
+ EOL
107
+ end
108
+
109
+ def tasks_register
110
+ <<-EOL
111
+ Examples:
112
+
113
+ $ ufo tasks register
114
+ All the task defintiions in ufo/output registered.
115
+ EOL
116
+ end
117
+
118
+ def ship
119
+ <<-EOL
120
+ Examples:
121
+
122
+ To build the docker image, generate the task definition and ship it, run:
123
+
124
+ $ ufo ship hi-web-prod
125
+
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:
127
+
128
+ $ ufo ship hi-web-prod-1 --task hi-web-prod
129
+
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.
131
+
132
+ The prommpt can be bypassed by specifying a valid `--target-group` option or specifying the `---no-elb-prompt` option.
133
+
134
+ $ ufo ship hi-web-prod --target-group arn:aws:elasticloadbalancing:us-east-1:123456789:targetgroup/hi-web-prod/jsdlfjsdkd
135
+
136
+ $ ufo ship hi-web-prod --no-elb-prompt
137
+ EOL
138
+ end
139
+
140
+ def destroy
141
+ <<-EOL
142
+ Examples:
143
+
144
+ Destroys the service. It will automatcally set the desired task size to 0 and stop all task so the destory happens in one command.
145
+
146
+ $ ufo destroy hi-web-prod
147
+
148
+ EOL
149
+ end
150
+
151
+ def scale
152
+ <<-EOL
153
+ Examples:
154
+
155
+ Scales the service. Simple wrapper for `aws ecs update-service --service xxx ----desired-count xxx`
156
+
157
+ $ ufo scale hi-web-prod 5
158
+
159
+ EOL
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end