ufo 2.3.0 → 3.0.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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +57 -0
  3. data/.gitmodules +3 -0
  4. data/.rspec +1 -0
  5. data/CHANGELOG.md +16 -1
  6. data/Gemfile.lock +16 -3
  7. data/README.md +5 -1
  8. data/docs/_docs/auto-completion.md +27 -0
  9. data/docs/_docs/automated-cleanup.md +1 -1
  10. data/docs/_docs/conventions.md +2 -2
  11. data/docs/_docs/helpers.md +6 -6
  12. data/docs/_docs/run-in-pieces.md +15 -8
  13. data/docs/_docs/settings.md +61 -49
  14. data/docs/_docs/structure.md +2 -2
  15. data/docs/_docs/tutorial-ufo-docker-build.md +10 -3
  16. data/docs/_docs/tutorial-ufo-init.md +48 -16
  17. data/docs/_docs/tutorial-ufo-ship.md +14 -7
  18. data/docs/_docs/tutorial-ufo-ships.md +1 -1
  19. data/docs/_docs/tutorial-ufo-tasks-build.md +23 -14
  20. data/docs/_docs/ufo-deploy.md +30 -0
  21. data/docs/_docs/ufo-docker-base.md +3 -3
  22. data/docs/_docs/ufo-docker-build.md +3 -3
  23. data/docs/_docs/ufo-docker-push.md +43 -0
  24. data/docs/_docs/ufo-env.md +17 -15
  25. data/docs/_docs/ufo-init.md +14 -1
  26. data/docs/_docs/ufo-scale.md +2 -4
  27. data/docs/_docs/ufo-ships.md +2 -2
  28. data/docs/_docs/variables.md +6 -6
  29. data/docs/_includes/commands.html +4 -4
  30. data/docs/_includes/subnav.html +3 -0
  31. data/docs/_includes/summary.html +2 -2
  32. data/docs/_includes/ufo-ship-options.md +0 -2
  33. data/docs/docs.md +5 -1
  34. data/docs/quick-start.md +19 -10
  35. data/lib/{starter_project → template}/.env +0 -0
  36. data/lib/template/.ufo/settings.yml.tt +27 -0
  37. data/lib/{starter_project/ufo/task_definitions.rb → template/.ufo/task_definitions.rb.tt} +0 -0
  38. data/lib/{starter_project/ufo → template/.ufo}/templates/main.json.erb +0 -0
  39. data/lib/{starter_project/ufo → template/.ufo}/variables/base.rb +0 -0
  40. data/lib/{starter_project/ufo → template/.ufo}/variables/development.rb +0 -0
  41. data/lib/{starter_project/ufo → template/.ufo}/variables/production.rb +0 -0
  42. data/lib/{starter_project → template}/Dockerfile +0 -0
  43. data/lib/{starter_project/bin/deploy → template/bin/deploy.tt} +0 -0
  44. data/lib/ufo.rb +9 -2
  45. data/lib/ufo/cli.rb +34 -29
  46. data/lib/ufo/completer.rb +86 -64
  47. data/lib/ufo/core.rb +42 -0
  48. data/lib/ufo/default.rb +4 -6
  49. data/lib/ufo/default/settings.yml +24 -22
  50. data/lib/ufo/deploy.rb +0 -0
  51. data/lib/ufo/docker.rb +12 -2
  52. data/lib/ufo/docker/builder.rb +19 -49
  53. data/lib/ufo/docker/cleaner.rb +4 -2
  54. data/lib/ufo/docker/dockerfile.rb +1 -2
  55. data/lib/ufo/docker/pusher.rb +53 -0
  56. data/lib/ufo/dsl.rb +1 -2
  57. data/lib/ufo/dsl/helper.rb +3 -4
  58. data/lib/ufo/dsl/outputter.rb +1 -1
  59. data/lib/ufo/dsl/task_definition.rb +17 -37
  60. data/lib/ufo/ecr/auth.rb +22 -2
  61. data/lib/ufo/ecs.rb +5 -0
  62. data/lib/ufo/ecs/service.rb +21 -0
  63. data/lib/ufo/help/completion.md +1 -1
  64. data/lib/ufo/help/completion_script.md +1 -1
  65. data/lib/ufo/help/deploy.md +14 -0
  66. data/lib/ufo/help/docker/name.md +13 -2
  67. data/lib/ufo/help/docker/push.md +11 -0
  68. data/lib/ufo/init.rb +48 -65
  69. data/lib/ufo/log_group.rb +5 -2
  70. data/lib/ufo/sequence.rb +27 -0
  71. data/lib/ufo/setting.rb +18 -8
  72. data/lib/ufo/ship.rb +23 -46
  73. data/lib/ufo/tasks/builder.rb +8 -11
  74. data/lib/ufo/tasks/register.rb +2 -3
  75. data/lib/ufo/upgrade3.rb +64 -0
  76. data/lib/ufo/util.rb +0 -2
  77. data/lib/ufo/version.rb +1 -1
  78. data/spec/fixtures/home_existing/.docker/config.json +1 -1
  79. data/spec/fixtures/settings.yml +23 -0
  80. data/spec/lib/cli_spec.rb +1 -9
  81. data/spec/lib/completion_spec.rb +18 -0
  82. data/spec/lib/core_spec.rb +16 -0
  83. data/spec/lib/ecr_auth_spec.rb +1 -3
  84. data/spec/lib/ecr_cleaner_spec.rb +1 -3
  85. data/spec/lib/setting_spec.rb +12 -0
  86. data/spec/lib/ship_spec.rb +2 -4
  87. data/spec/lib/task_spec.rb +0 -2
  88. data/spec/spec_helper.rb +12 -2
  89. data/ufo.gemspec +2 -0
  90. metadata +47 -13
  91. data/lib/starter_project/ufo/settings.yml +0 -18
  92. data/lib/ufo/env.rb +0 -18
  93. data/lib/ufo/help/sub/goodbye.md +0 -5
@@ -1,58 +1,73 @@
1
- # Code Explanation. This is mainly focused on the run method.
2
- #
3
- # There are 3 main branches of logic for completion:
4
- #
5
- # 1. top-level commands - when there are zero completed words
6
- # 2. params completion - when a command has some required params
7
- # 3. options completion - when we have finished auto-completing the top-level command and required params, the rest of the completion words will be options
8
- #
9
- # Terms:
10
- #
11
- # params - these are params in the command itself. Example: for the method `scale(service, count)` the params would be `service, count`.
12
- # options - these are cli options flags. Examples: --noop, --verbose
13
- #
14
- # When we are done processing method params, the completion will be only options. When the detected params size is greater than the arity we are have finished auto-completing the parameters in the method declaration. For example, say you had a method for a CLI command with the following form:
15
- #
16
- # scale(service, count) = arity of 2
17
- #
18
- # ufo scale service count [TAB] # there are 3 params including the "scale" command
19
- #
20
- # So the completion will be something like:
21
- #
22
- # --noop --verbose etc
23
- #
24
- # A note about artity values:
25
- #
26
- # We are using the arity of the command method to determine if we have finish auto-completing the params completion. When the ruby method has a splat param, it's arity will be negative. Here are some example methods and their arities.
27
- #
28
- # ship(service) = 1
29
- # scale(service, count) = 2
30
- # ships(*services) = -1
31
- # foo(example, *rest) = -2
32
- #
33
- # Fortunately, negative and positive arity values are processed the same way. So we take simply take the abs of the arity.
34
- #
35
- # To test:
36
- #
37
- # ufo completion
38
- # ufo completion hello
39
- # ufo completion hello name
40
- # ufo completion hello name --
41
- # ufo completion hello name --noop
42
- #
43
- # ufo completion
44
- # ufo completion sub:goodbye
45
- # ufo completion sub:goodbye name
46
- #
47
- # Note when testing, the first top-level word must be an exact match
48
- #
49
- # ufo completion hello # works fine
50
- # ufo completion he # incomplete, this will just break
51
- #
52
- # The completion assumes that the top-level word that is being passed in
53
- # from completor/scripts.sh will always match exactly. This must be the
54
- # case. For parameters, the word does not have to match exactly.
55
- #
1
+ =begin
2
+ Code Explanation:
3
+
4
+ There are 3 types of things to auto-complete:
5
+
6
+ 1. command: the command itself
7
+ 2. parameters: command parameters.
8
+ 3. options: command options
9
+
10
+ Here's an example:
11
+
12
+ mycli hello name --from me
13
+
14
+ * command: hello
15
+ * parameters: name
16
+ * option: --from
17
+
18
+ When command parameters are done processing, the remaining completion words will be options. We can tell that the command params are completed based on the method arity.
19
+
20
+ ## Arity
21
+
22
+ For example, say you had a method for a CLI command with the following form:
23
+
24
+ ufo scale service count --cluster development
25
+
26
+ It's equivalent ruby method:
27
+
28
+ scale(service, count) = has an arity of 2
29
+
30
+ So typing:
31
+
32
+ ufo scale service count [TAB] # there are 3 parameters including the "scale" command according to Thor's CLI processing.
33
+
34
+ So the completion should only show options, something like this:
35
+
36
+ --noop --verbose --cluster
37
+
38
+ ## Splat Arguments
39
+
40
+ When the ruby method has a splat argument, it's arity is negative. Here are some example methods and their arities.
41
+
42
+ ship(service) = 1
43
+ scale(service, count) = 2
44
+ ships(*services) = -1
45
+ foo(example, *rest) = -2
46
+
47
+ Fortunately, negative and positive arity values are processed the same way. So we take simply take the absolute value of the arity and process it the same.
48
+
49
+ Here are some test cases, hit TAB after typing the command:
50
+
51
+ ufo completion
52
+ ufo completion hello
53
+ ufo completion hello name
54
+ ufo completion hello name --
55
+ ufo completion hello name --noop
56
+
57
+ ufo completion
58
+ ufo completion sub:goodbye
59
+ ufo completion sub:goodbye name
60
+
61
+ ## Subcommands and Thor::Group Registered Commands
62
+
63
+ Sometimes the commands are not simple thor commands but are subcommands or Thor::Group commands. A good specific example is the ufo tool.
64
+
65
+ * regular command: ufo ship
66
+ * subcommand: ufo docker
67
+ * Thor::Group command: ufo init
68
+
69
+ Auto-completion accounts for each of these type of commands.
70
+ =end
56
71
  module Ufo
57
72
  class Completer
58
73
  autoload :Script, 'ufo/completer/script'
@@ -79,10 +94,10 @@ module Ufo
79
94
 
80
95
  # will only get to here if command aws found (above)
81
96
  arity = @command_class.instance_method(@current_command).arity.abs
82
- if @params.size <= arity
83
- puts params_completion
84
- else
97
+ if @params.size > arity or thor_group_command?
85
98
  puts options_completion
99
+ else
100
+ puts params_completion
86
101
  end
87
102
  end
88
103
 
@@ -90,8 +105,13 @@ module Ufo
90
105
  @command_class.subcommands.include?(command)
91
106
  end
92
107
 
108
+ # hacky way to detect that command is a registered Thor::Group command
109
+ def thor_group_command?
110
+ command_params(raw=true) == [[:rest, :args]]
111
+ end
112
+
93
113
  def found?(command)
94
- public_methods = @command_class.public_instance_methods - Object.methods
114
+ public_methods = @command_class.public_instance_methods(false)
95
115
  command && public_methods.include?(command.to_sym)
96
116
  end
97
117
 
@@ -103,17 +123,19 @@ module Ufo
103
123
  commands.keys
104
124
  end
105
125
 
106
- def params_completion
107
- method_params = @command_class.instance_method(@current_command).parameters
126
+ def command_params(raw=false)
127
+ params = @command_class.instance_method(@current_command).parameters
108
128
  # Example:
109
129
  # >> Sub.instance_method(:goodbye).parameters
110
130
  # => [[:req, :name]]
111
131
  # >>
112
- method_params.map!(&:last)
132
+ raw ? params : params.map!(&:last)
133
+ end
113
134
 
135
+ def params_completion
114
136
  offset = @params.size - 1
115
- offset_params = method_params[offset..-1]
116
- method_params[offset..-1].first
137
+ offset_params = command_params[offset..-1]
138
+ command_params[offset..-1].first
117
139
  end
118
140
 
119
141
  def options_completion
@@ -0,0 +1,42 @@
1
+ require 'pathname'
2
+ require 'yaml'
3
+
4
+ module Ufo
5
+ module Core
6
+ def check_task_definition!(task_definition)
7
+ task_definition_path = "#{Ufo.root}/.ufo/output/#{task_definition}.json"
8
+ unless File.exist?(task_definition_path)
9
+ puts "ERROR: Unable to find the task definition at #{task_definition_path}.".colorize(:red)
10
+ puts "Are you sure you have defined it in ufo/template_definitions.rb and it has been generated correctly in .ufo/output?".colorize(:red)
11
+ puts "If you are calling `ufo deploy` directly, you might want to generate the task definition first with `ufo tasks build`."
12
+ exit
13
+ end
14
+ end
15
+
16
+ def root
17
+ path = ENV['UFO_ROOT'] || '.'
18
+ Pathname.new(path)
19
+ end
20
+
21
+ @@env = nil
22
+ def env
23
+ return @@env if @@env
24
+ ufo_env = env_from_profile(ENV['AWS_PROFILE']) || 'development'
25
+ ufo_env = ENV['UFO_ENV'] if ENV['UFO_ENV'] # highest precedence
26
+ @@env = ufo_env
27
+ end
28
+
29
+ private
30
+ # Do not use the Setting class to load the profile because it can cause an
31
+ # infinite loop then if we decide to use Ufo.env from within settings class.
32
+ def env_from_profile(aws_profile)
33
+ data = YAML.load_file("#{Ufo.root}/.ufo/settings.yml")
34
+ env = data.find do |_env, setting|
35
+ setting ||= {}
36
+ profiles = setting['aws_profiles']
37
+ profiles && profiles.include?(aws_profile)
38
+ end
39
+ env.first if env
40
+ end
41
+ end
42
+ end
@@ -8,19 +8,17 @@ module Ufo
8
8
  #
9
9
  # So @options must be set
10
10
  module Default
11
- # The default cluster normally defaults to the UFO_ENV value.
11
+ # The default cluster normally defaults to the Ufo.env value.
12
12
  # But it can be overriden by ufo/settings.yml ufo_env_cluster_map
13
13
  #
14
14
  # More info: http://ufoships.com/docs/settings/
15
15
  def default_cluster
16
- #
17
-
18
16
  map = setting.data["ufo_env_cluster_map"]
19
17
  if map
20
- ecs_cluster = map[UFO_ENV] || map["default"]
18
+ ecs_cluster = map[Ufo.env] || map["default"]
21
19
  end
22
20
 
23
- ecs_cluster || UFO_ENV
21
+ ecs_cluster || Ufo.env
24
22
  end
25
23
 
26
24
  # These default service values only are used when a service is created by `ufo`
@@ -41,7 +39,7 @@ module Ufo
41
39
  end
42
40
 
43
41
  def setting
44
- @setting ||= Setting.new(@options[:project_root])
42
+ @setting ||= Setting.new
45
43
  end
46
44
  end
47
45
  end
@@ -1,25 +1,27 @@
1
1
  # More info: http://ufoships.com/docs/ufo-settings/
2
- #
3
- # image:
4
- # clean_keep: 30
5
- # ecr_keep: 30
2
+ # The base config is specially treated. It gets included the other environments automatically.
3
+ # Yaml also directly supports merging with & and <<* syntax but doing it automatically
4
+ # for a cleaner syntax.
5
+ base:
6
+ # image:
7
+ # clean_keep: 30
8
+ # ecr_keep: 30
9
+ # defaults when an new ECS service is created by ufo ship
10
+ new_service:
11
+ maximum_percent: 200
12
+ minimum_healthy_percent: 100
13
+ desired_count: 1
6
14
 
7
- # aws_profile_ufo_env_map:
8
- # default: prod
9
- # # More examples:
10
- # aws_profile1: prod
11
- # aws_profile2: stag
12
- # aws_profile3: dev
15
+ development:
16
+ # cluster: dev
17
+ # When you have AWS_PROFILE set to one of these values, ufo will switch to the desired
18
+ # environment. This prevents you from switching AWS_PROFILE, forgetting to
19
+ # also switch UFO_ENV, and accidentally deploying to production vs development.
20
+ # aws_profiles:
21
+ # - dev_profile1
22
+ # - dev_profile2
13
23
 
14
- # ufo_env_cluster_map:
15
- # default: prod
16
- # # More examples:
17
- # aws_profile1: prod
18
- # aws_profile2: stag
19
- # aws_profile3: dev
20
-
21
- # defaults when an new ECS service is created by ufo ship
22
- new_service:
23
- maximum_percent: 200
24
- minimum_healthy_percent: 100
25
- desired_count: 1
24
+ production:
25
+ # cluster: prod
26
+ # aws_profiles:
27
+ # - prod_profile
File without changes
@@ -1,6 +1,7 @@
1
1
  module Ufo
2
2
  class Docker < Command
3
3
  autoload :Builder, 'ufo/docker/builder'
4
+ autoload :Pusher, 'ufo/docker/pusher'
4
5
  autoload :Dockerfile, 'ufo/docker/dockerfile'
5
6
  autoload :Cleaner, 'ufo/docker/cleaner'
6
7
 
@@ -10,7 +11,16 @@ module Ufo
10
11
  def build
11
12
  builder = Docker::Builder.new(options)
12
13
  builder.build
13
- builder.push if options[:push]
14
+ push if options[:push]
15
+ end
16
+
17
+ desc "push IMAGE", "push the docker image"
18
+ long_desc Help.text("docker:push")
19
+ option :push, type: :boolean, default: false
20
+ def push(full_image_name=nil)
21
+ # full_image_name of nil results in defaulting to the last built image by ufo docker build
22
+ pusher = Docker::Pusher.new(full_image_name, options)
23
+ pusher.push
14
24
  end
15
25
 
16
26
  desc "base", "builds docker image from Dockerfile.base and update current Dockerfile"
@@ -28,7 +38,7 @@ module Ufo
28
38
  Ecr::Cleaner.new(builder.image_name, options.merge(tag_prefix: "base")).cleanup
29
39
  end
30
40
 
31
- desc "name", "displays the full docker image with tag that will be generated"
41
+ desc "name", "displays the full docker image with tag that was last generated."
32
42
  option :generate, type: :boolean, default: false, desc: "Generate a name without storing it"
33
43
  long_desc Help.text("docker:name")
34
44
  def name
@@ -1,19 +1,20 @@
1
- module Ufo
2
- class Docker::Builder
3
- include Util
1
+ require 'active_support/core_ext/module/delegation'
4
2
 
3
+ class Ufo::Docker
4
+ class Builder
5
+ include Ufo::Util
6
+
7
+ delegate :push, to: :pusher
5
8
  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
9
+ builder = Builder.new(options) # outside if because it need builder.full_image_name
10
+ builder.build
11
+ pusher = Docker::Pusher.new(nil, options)
12
+ pusher.push
11
13
  builder
12
14
  end
13
15
 
14
16
  def initialize(options={})
15
17
  @options = options
16
- @project_root = options[:project_root] || '.'
17
18
  @dockerfile = options[:dockerfile] || 'Dockerfile'
18
19
  @image_namespace = options[:image_namespace] || 'ufo'
19
20
  end
@@ -21,13 +22,12 @@ module Ufo
21
22
  def build
22
23
  start_time = Time.now
23
24
  store_full_image_name
24
- update_auth_token # call after store_full_image_name
25
25
 
26
26
  command = "docker build -t #{full_image_name} -f #{@dockerfile} ."
27
27
  say "Building docker image with:".green
28
28
  say " #{command}".green
29
29
  check_dockerfile_exists
30
- command = "cd #{@project_root} && #{command}"
30
+ command = "cd #{Ufo.root} && #{command}"
31
31
  success = execute(command, use_system: true)
32
32
  unless success
33
33
  puts "ERROR: The docker image fail to build. Are you sure the docker daemon is available? Try running: docker version".colorize(:red)
@@ -38,44 +38,17 @@ module Ufo
38
38
  say "Docker image #{full_image_name} built. " + "Took #{pretty_time(took)}.".green
39
39
  end
40
40
 
41
- def push
42
- update_auth_token
43
- start_time = Time.now
44
- message = "Pushed #{full_image_name} docker image."
45
- if @options[:noop]
46
- message = "NOOP #{message}"
47
- else
48
- command = "docker push #{full_image_name}"
49
- puts "=> #{command}".colorize(:green)
50
- success = execute(command, use_system: true)
51
- unless success
52
- puts "ERROR: The docker image fail to push.".colorize(:red)
53
- exit 1
54
- end
55
- end
56
- took = Time.now - start_time
57
- message << " Took #{pretty_time(took)}.".green
58
- puts message unless @options[:mute]
41
+ def pusher
42
+ @pusher ||= Pusher.new(full_image_name, @options)
59
43
  end
60
44
 
61
45
  def check_dockerfile_exists
62
- unless File.exist?("#{@project_root}/#{@dockerfile}")
46
+ unless File.exist?("#{Ufo.root}/#{@dockerfile}")
63
47
  puts "#{@dockerfile} does not exist. Are you sure it exists?"
64
48
  exit 1
65
49
  end
66
50
  end
67
51
 
68
- def update_auth_token
69
- return unless ecr_image?
70
- repo_domain = "https://#{image_name.split('/').first}"
71
- auth = Ecr::Auth.new(repo_domain)
72
- auth.update
73
- end
74
-
75
- def ecr_image?
76
- full_image_name =~ /\.amazonaws\.com/
77
- end
78
-
79
52
  # full_image - does not include the tag
80
53
  def image_name
81
54
  setting.data["image"]
@@ -83,10 +56,7 @@ module Ufo
83
56
 
84
57
  # full_image - includes the tag
85
58
  def full_image_name
86
- if @options[:generate]
87
- return generate_name # name already has a newline
88
- end
89
-
59
+ return generate_name if @options[:generate]
90
60
  return "tongueroo/hi:ufo-12345678" if ENV['TEST']
91
61
 
92
62
  unless File.exist?(docker_name_path)
@@ -113,7 +83,7 @@ module Ufo
113
83
 
114
84
  def docker_name_path
115
85
  # output gets entirely wiped by tasks builder so dotn use that folder
116
- "#{@project_root}/ufo/data/docker_image_name_#{@image_namespace}.txt"
86
+ "#{Ufo.root}/.ufo/data/docker_image_name_#{@image_namespace}.txt"
117
87
  end
118
88
 
119
89
  def timestamp
@@ -123,16 +93,16 @@ module Ufo
123
93
  def git_sha
124
94
  return @git_sha if @git_sha
125
95
  # always call this and dont use the execute method because of the noop option
126
- @git_sha = `cd #{@project_root} && git rev-parse --short HEAD`
96
+ @git_sha = `cd #{Ufo.root} && git rev-parse --short HEAD`
127
97
  @git_sha.strip!
128
98
  end
129
99
 
130
100
  def setting
131
- @setting ||= Setting.new(@project_root)
101
+ @setting ||= Ufo::Setting.new(Ufo.root)
132
102
  end
133
103
 
134
104
  def update_dockerfile
135
- dockerfile = Docker::Dockerfile.new(full_image_name, @options)
105
+ dockerfile = Dockerfile.new(full_image_name, @options)
136
106
  dockerfile.update
137
107
  end
138
108