keel 0.1.5 → 0.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3837deed0bba2d9618d452af738a4dca69c109e5
4
- data.tar.gz: a069e3ba4d3bf4035b49d98ec60369f54b5a2669
3
+ metadata.gz: e0dcc518483a9cd8647f25a82cbfc1188a77b803
4
+ data.tar.gz: 34caf4a598ff2e6f5ca8d714d663faae01f7daa0
5
5
  SHA512:
6
- metadata.gz: 55344a32cc6baf594e379ce440e00317435fb0e0e53d54276a1030604c2b3145c6ddb63863c012b5bdd648161dc5586a8d85728975a7fad9a818f28d846df064
7
- data.tar.gz: 73c7f65984d998f7b177263321c6504e22c9b4b3afd5ca2f67d45393ea89bea8f517ce883d95778f3619fcb2d5fa11c24ad738fc75088ccc3cc126dfee81552b
6
+ metadata.gz: aa58466ca36a0398ef89e3e539bb7882a4d3e15c824031539ecca2c8daeb6b05564a0b250598a7ec1a1ff82907cdcfa4d2a825b43d0f8141198e9baca56e2cfa
7
+ data.tar.gz: 9d881c1b7dc35b9c443be19f0e561c863e25293c7485da375f2cc207cb11fa9e254612c0022c1fd2ab4ede9b4ba67d3a7b8d1f8a02a311970a64a54c7ba7b3c5
data/Rakefile CHANGED
@@ -1,5 +1,6 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rake/testtask"
3
+ import "./lib/tasks/keel.rake"
3
4
 
4
5
  Rake::TestTask.new(:test) do |t|
5
6
  t.libs << "test"
data/keel.gemspec CHANGED
@@ -22,6 +22,8 @@ Gem::Specification.new do |spec|
22
22
  spec.add_development_dependency "bundler", "~> 1.11"
23
23
  spec.add_development_dependency "rake", "~> 10.0"
24
24
  spec.add_development_dependency "minitest", "~> 5.0"
25
+ spec.add_development_dependency "guard-minitest", "~>2.0"
26
+ spec.add_development_dependency "guard", "~>2.0"
25
27
 
26
28
  spec.add_runtime_dependency "inquirer", "~> 0.2"
27
29
  spec.add_runtime_dependency "colorize", "~> 0.7"
@@ -4,16 +4,17 @@ module Keel::GCloud
4
4
  # A helper class to run system commands and handle interrupts.
5
5
  #
6
6
  class Cli
7
+ @@wait_sequence = %w[| / - \\]
8
+
7
9
  def execute command
8
10
  begin
9
11
  out = ""
10
12
  Open3.popen3(command) do |stdout, stderr, stdin, thread|
11
- # TODO do smarter things with status and stdout
12
- while line=stderr.gets do
13
- out += line
14
- print '.'
15
- end
16
- print "\n"
13
+ show_wait_spinner{
14
+ while line=stderr.gets do
15
+ out += line
16
+ end
17
+ }
17
18
  raise "error while processing. " + out unless thread.value.success?
18
19
  return out
19
20
  end
@@ -29,5 +30,23 @@ module Keel::GCloud
29
30
  puts 'Task interrupted.'
30
31
  end
31
32
  end
33
+
34
+ # better wait cursors, thanks http://stackoverflow.com/questions/10262235/printing-an-ascii-spinning-cursor-in-the-console
35
+ def show_wait_spinner(fps=10)
36
+ chars = %w[| / - \\]
37
+ delay = 1.0/fps
38
+ iter = 0
39
+ spinner = Thread.new do
40
+ while iter do # Keep spinning until told otherwise
41
+ print chars[(iter+=1) % chars.length]
42
+ sleep delay
43
+ print "\b"
44
+ end
45
+ end
46
+ yield.tap{ # After yielding to the block, save the return value
47
+ iter = false # Tell the thread to exit, cleaning up after itself…
48
+ spinner.join # …and wait for it to do so.
49
+ } # Use the block's return value as the method's
50
+ end
32
51
  end
33
52
  end
@@ -25,7 +25,14 @@ module Keel::GCloud
25
25
 
26
26
  def initialize
27
27
  @cli = Cli.new
28
- @config = YAML.load_file(Rails.root.join('config', 'gcloud.yml'))
28
+
29
+ path = ''
30
+ if defined? Rails
31
+ path = Rails.root.join('config', 'gcloud.yml')
32
+ else
33
+ path = File.join(Dir.pwd, 'config.yaml')
34
+ end
35
+ @config = YAML.load_file(path)
29
36
 
30
37
  @app_name = @config[:app][:name]
31
38
  @compute_region = @config[:compute][:region]
@@ -2,7 +2,7 @@ module Keel::GCloud
2
2
  module Kubernetes
3
3
  #
4
4
  # A class to represent a Kubernetes Deployment
5
- class Deployment < ReplicationController
5
+ class Deployment < PodManager
6
6
 
7
7
  #
8
8
  # Fetches the correct deployment or replication controller from Kubernetes.
@@ -14,7 +14,7 @@ module Keel::GCloud
14
14
  def self.fetch_all env, app
15
15
  command = "kubectl get deployment --namespace=#{env} -l app=#{app} -o yaml"
16
16
  rcs_yaml = YAML.load Cli.new.execute(command)
17
- return false unless rcs_yaml["items"].count > 0
17
+ return [] unless rcs_yaml["items"].count > 0
18
18
  self.from_yaml rcs_yaml
19
19
  end
20
20
 
@@ -45,11 +45,17 @@ module Keel::GCloud
45
45
  # Fetches all the pods from Kubernetes.
46
46
  #
47
47
  # @param env [String] the namespace/environment for which to fetch the pods
48
- # @param app [String] the app for which to fetch the pods
48
+ # @param app [String] the app for which to fetch the pods. [optional] Hash with multiple selectors
49
49
  # @return [Hash] the parsed result of the API call
50
50
  #
51
- def self.fetch_all env, app
52
- command = "kubectl get po --namespace=#{env} -l app=#{app} -o yaml"
51
+ def self.fetch_all env, selector
52
+ if selector.is_a? Hash
53
+ selector = selector.map{|k,v| "#{k}=#{v}"}.join(',')
54
+ else
55
+ selector = "app=#{selector}"
56
+ end
57
+
58
+ command = "kubectl get po --namespace=#{env} -l #{selector} -o yaml"
53
59
  rcs_yaml = YAML.load Cli.new.execute(command)
54
60
  return false unless rcs_yaml
55
61
 
@@ -0,0 +1,86 @@
1
+ require 'yaml'
2
+
3
+ module Keel::GCloud
4
+ module Kubernetes
5
+ #
6
+ # A class to represent the shared characteristics of a Kubernetes pod
7
+ # manager (eg. Deployment or Replication Controller)
8
+ #
9
+ class PodManager
10
+ attr_accessor :containers, :name, :namespace, :original, :uid
11
+
12
+ def initialize **params
13
+ @containers = params[:containers]
14
+ @name = params[:name]
15
+ @namespace = params[:namespace]
16
+ @uid = params[:uid]
17
+
18
+ @original = params[:original]
19
+ @original['metadata'].delete 'creationTimestamp'
20
+ end
21
+
22
+ #
23
+ # Parses the returned YAML into objects of the ReplicationController class.
24
+ #
25
+ # @param yaml [Hash] the parsed result of the API call
26
+ # @return [Array<ReplicationController>] an array of ReplicationController objects
27
+ #
28
+ def self.from_yaml yaml
29
+ yaml['items'].map do |item|
30
+ params = {
31
+ containers: item['spec']['template']['spec']['containers'],
32
+ name: item['metadata']['name'],
33
+ namespace: item['metadata']['namespace'],
34
+ original: item,
35
+ uid: item['metadata']['uid'],
36
+ }
37
+ self.new params
38
+ end
39
+ end
40
+
41
+ #
42
+ # Replaces the controller's specifications with a new one.
43
+ #
44
+ # @param file [File] the new specifications file
45
+ # @return [Boolean] whether the call succeeded or not
46
+ #
47
+ def self.replace file
48
+ Cli.new.system_call "kubectl replace -f #{file}"
49
+ end
50
+
51
+ #
52
+ # Get the YAML representation of the controller.
53
+ #
54
+ # @return [String] the YAML format
55
+ #
56
+ def to_yaml
57
+ self.original.to_yaml
58
+ end
59
+
60
+ #
61
+ # Writes the current specifications to a file.
62
+ #
63
+ # @param filename [String] the name of the file to write to
64
+ # @return [Boolean] result of the operation
65
+ #
66
+ def to_file filename
67
+ File.open(filename, 'w') do |io|
68
+ io.write self.to_yaml
69
+ end
70
+ end
71
+
72
+ #
73
+ # Updates the specifications of a controller on Kubernetes
74
+ # with the latest specs.
75
+ #
76
+ # (see #to_file)
77
+ # (see #replace)
78
+ #
79
+ def update
80
+ tmp_file = Rails.root.join('tmp', 'deployment-rc.yml')
81
+ self.to_file tmp_file
82
+ self.class.replace tmp_file
83
+ end
84
+ end
85
+ end
86
+ end
@@ -7,40 +7,9 @@ module Keel::GCloud
7
7
  # It is a simplified view of what Kubernetes returns with only
8
8
  # the necessary information required to perform the operations needed.
9
9
  #
10
- class ReplicationController
11
- attr_accessor :containers, :name, :namespace, :original, :uid
12
-
13
- def initialize **params
14
- @containers = params[:containers]
15
- @name = params[:name]
16
- @namespace = params[:namespace]
17
- @uid = params[:uid]
18
-
19
- @original = params[:original]
20
- @original['metadata'].delete 'creationTimestamp'
21
- end
22
-
23
- #
24
- # Parses the returned YAML into objects of the ReplicationController class.
25
- #
26
- # @param yaml [Hash] the parsed result of the API call
27
- # @return [Array<ReplicationController>] an array of ReplicationController objects
28
- #
29
- def self.from_yaml yaml
30
- yaml['items'].map do |item|
31
- params = {
32
- containers: item['spec']['template']['spec']['containers'],
33
- name: item['metadata']['name'],
34
- namespace: item['metadata']['namespace'],
35
- original: item,
36
- uid: item['metadata']['uid'],
37
- }
38
- self.new params
39
- end
40
- end
41
-
10
+ class ReplicationController < PodManager
42
11
  #
43
- # Fetches the correct deployment or replication controller from Kubernetes.
12
+ # Fetches the correct replication controller from Kubernetes.
44
13
  #
45
14
  # @param env [String] the namespace/environment for which to fetch the controllers
46
15
  # @param app [String] the app for which to fetch the controllers
@@ -49,67 +18,40 @@ module Keel::GCloud
49
18
  def self.fetch_all env, app
50
19
  command = "kubectl get rc --namespace=#{env} -l app=#{app} -o yaml"
51
20
  rcs_yaml = YAML.load Cli.new.execute(command)
52
- return false unless rcs_yaml["items"].count > 0
21
+ return [] unless rcs_yaml["items"].count > 0
53
22
  self.from_yaml rcs_yaml
54
23
  end
55
24
 
56
25
  #
57
- # Replaces the controller's specifications with a new one.
26
+ # Find a replication controller by name
58
27
  #
59
- # @param file [File] the new specifications file
60
- # @return [Boolean] whether the call succeeded or not
28
+ # @param env [String] the namespace/environment for which to fetch the controllers
29
+ # @param name [String] the name of the replication controller
30
+ # @return a ReplicationController
61
31
  #
62
- def self.replace file
63
- Cli.new.system_call "kubectl replace -f #{file}"
32
+ def self.find env, name
33
+ command = "kubectl get rc --namespace=#{env} #{name} -o yaml"
34
+ rcs_yaml = YAML.load Cli.new.execute(command)
35
+ return false unless rcs_yaml["items"].count > 0
36
+ self.from_yaml rcs_yaml
64
37
  end
65
38
 
66
- #
67
- # Get the YAML representation of the controller.
68
- #
69
- # @return [String] the YAML format
70
- #
71
- def to_yaml
72
- self.original.to_yaml
73
- end
74
39
 
75
- #
76
- # Writes the current specifications to a file.
77
- #
78
- # @param filename [String] the name of the file to write to
79
- # @return [Boolean] result of the operation
80
- #
81
- def to_file filename
82
- File.open(filename, 'w') do |io|
83
- io.write self.to_yaml
84
- end
85
- end
40
+ # #
41
+ # # Increments the number of replicas.
42
+ # #
43
+ # def increment_replica_count
44
+ # self.original['spec']['replicas'] += 1
45
+ # end
86
46
 
87
- #
88
- # Increments the number of replicas.
89
- #
90
- def increment_replica_count
91
- self.original['spec']['replicas'] += 1
92
- end
47
+ # #
48
+ # # Decrements the number of replicas.
49
+ # #
50
+ # def decrement_replica_count
51
+ # self.original['spec']['replicas'] -= 1
52
+ # end
93
53
 
94
- #
95
- # Decrements the number of replicas.
96
- #
97
- def decrement_replica_count
98
- self.original['spec']['replicas'] -= 1
99
- end
100
54
 
101
- #
102
- # Updates the specifications of a controller on Kubernetes
103
- # with the latest specs.
104
- #
105
- # (see #to_file)
106
- # (see #replace)
107
- #
108
- def update
109
- tmp_file = Rails.root.join('tmp', 'deployment-rc.yml')
110
- self.to_file tmp_file
111
- self.class.replace tmp_file
112
- end
113
55
  end
114
56
  end
115
57
  end
@@ -1,4 +1,5 @@
1
1
  require_relative 'kubernetes/namespace'
2
+ require_relative 'kubernetes/pod_manager'
2
3
  require_relative 'kubernetes/replication_controller'
3
4
  require_relative 'kubernetes/deployment'
4
5
  require_relative 'kubernetes/pod'
@@ -33,7 +33,7 @@ module Keel::GCloud
33
33
  # @param default [String, nil] the default choice
34
34
  #
35
35
  def prompt_for_namespace namespaces, default=nil
36
- return default unless default.blank?
36
+ return default unless default.nil? || default.empty?
37
37
 
38
38
  options = namespaces.map { |namespace| namespace.name } - ['kube-system']
39
39
  if options.count > 1
@@ -51,7 +51,7 @@ module Keel::GCloud
51
51
  # @param default [String, nil] the default choice
52
52
  #
53
53
  def prompt_for_label default=nil
54
- return default unless default.blank?
54
+ return default unless default.nil? || default.empty?
55
55
 
56
56
  # Get current git SHA
57
57
  current_sha = `git rev-parse --short HEAD`.lines.first.split(' ')[0]
@@ -65,7 +65,7 @@ module Keel::GCloud
65
65
  # @param default [String, nil] the default choice
66
66
  #
67
67
  def prompt_for_database_url default=nil
68
- return default unless default.blank?
68
+ return default unless default.nil? || default.empty?
69
69
 
70
70
  Ask.input 'Database URL'
71
71
  end
@@ -77,7 +77,7 @@ module Keel::GCloud
77
77
  # @param default [String, nil] the default choice
78
78
  #
79
79
  def prompt_for_secret_key default=nil
80
- return default unless default.blank?
80
+ return default unless default.nil? || default.empty?
81
81
 
82
82
  Ask.input 'Secret key'
83
83
  end
data/lib/keel/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Keel
2
- VERSION = "0.1.5" # :nodoc:
2
+ VERSION = "0.1.6" # :nodoc:
3
3
  end
data/lib/tasks/keel.rake CHANGED
@@ -50,76 +50,87 @@ namespace :keel do
50
50
  #rc_type = :replication_controller
51
51
 
52
52
  # Retrieve a replication controller configuration from the cluster
53
- rcs = Keel::GCloud::Kubernetes::ReplicationController.fetch_all(deploy_env, app) || Keel::GCloud::Kubernetes::Deployment.fetch_all(deploy_env, app)
53
+ rcs = Keel::GCloud::Kubernetes::ReplicationController.fetch_all(deploy_env, app) + Keel::GCloud::Kubernetes::Deployment.fetch_all(deploy_env, app)
54
54
 
55
55
  unless rcs
56
56
  message = 'Unable to connect to Kubernetes, please try again later...'
57
57
  prompter.print message, :error
58
58
  abort
59
59
  end
60
- rc = rcs.first
61
60
 
62
- unless rc
61
+ unless rcs.first
63
62
  message = "Could not find a replication controller for the \"#{deploy_env}\" environment"
64
63
  prompter.print message, :error
65
64
  abort
66
65
  end
67
66
 
68
- # Prep deployment:
69
- # 1. Update image
70
- container = rc.containers.first
71
- container['image'] = "#{config.container_app_image_path}:#{deploy_sha}"
72
-
73
- if rc.is_a? Keel::GCloud::Kubernetes::Deployment
74
- rc.update
75
- else
76
- # Additionally for replication controllers
77
- # 2. Update replica count
78
- # 3. Write out to a tmp file
79
- # 4. Replace the running controller
80
- # - this will create 1 new pod with the updated code
81
-
82
- # We can get away with first since it is a single container pod
83
-
84
- rc.increment_replica_count
85
- rc.update
86
-
87
- # Get a list of pods for the RC, this must be done pre-change
88
- pods = Keel::GCloud::Kubernetes::Pod.fetch_all deploy_env, app
89
- unless pods
90
- message = 'Unable to connect to Kubernetes, please try again later...'
91
- prompter.print message, :error
92
- abort
67
+
68
+ rcs.each do |rc|
69
+ puts "Inspecting configuration for deployment #{rc.name}"
70
+ # Prep deployment:
71
+ # 1. Update image
72
+ controller_has_app_pod = false
73
+ rc.containers.each do |container|
74
+ if container['image'].start_with? config.container_app_image_path
75
+ puts " Updating image #{container['image']}"
76
+ container['image'] = "#{config.container_app_image_path}:#{deploy_sha}"
77
+ controller_has_app_pod = true
78
+ else
79
+ puts " Skipping updating image #{container['image']}"
80
+ end
93
81
  end
82
+ next unless controller_has_app_pod
83
+ if rc.is_a? Keel::GCloud::Kubernetes::Deployment
84
+ rc.update
85
+ else
86
+ # Additionally for replication controllers
87
+ # 2. Update replica count
88
+ # 3. Write out to a tmp file
89
+ # 4. Replace the running controller
90
+ # - this will create 1 new pod with the updated code
91
+
92
+ # We can get away with first since it is a single container pod
93
+
94
+ rc.increment_replica_count
95
+ rc.update
96
+
97
+ # Get a list of pods for the RC, this must be done pre-change
98
+ pods = Keel::GCloud::Kubernetes::Pod.fetch_all deploy_env, rc.original['spec']['selector']
99
+ unless pods
100
+ message = 'Unable to connect to Kubernetes, please try again later...'
101
+ prompter.print message, :error
102
+ abort
103
+ end
94
104
 
95
- # Iterate over all pods, checking to see if they are running
96
- all_pods_running = false
97
- while !all_pods_running do
98
- prompter.print 'Waiting for new pods to start', :info
99
- sleep 5
100
- new_pods = Keel::GCloud::Kubernetes::Pod.fetch_all deploy_env, app
101
-
102
- all_pods_running = true
103
- new_pods.each do |pod|
104
- if !pod.running?
105
- prompter.print "Pod \"#{pod.name}\" is not running", :info
106
- all_pods_running = false
105
+ # Iterate over all pods, checking to see if they are running
106
+ all_pods_running = false
107
+ while !all_pods_running do
108
+ prompter.print 'Waiting for new pods to start', :info
109
+ sleep 5
110
+ new_pods = Keel::GCloud::Kubernetes::Pod.fetch_all deploy_env, rc.original['spec']['selector']
111
+
112
+ all_pods_running = true
113
+ new_pods.each do |pod|
114
+ if !pod.running?
115
+ prompter.print "Pod \"#{pod.name}\" is not running", :info
116
+ all_pods_running = false
117
+ end
107
118
  end
108
119
  end
109
- end
110
120
 
111
- # Nuke old pods
112
- pods.each do |pod|
113
- pod.delete
114
- sleep 3
115
- end
121
+ # Nuke old pods
122
+ pods.each do |pod|
123
+ pod.delete
124
+ sleep 3
125
+ end
116
126
 
117
- # Bring the replica count down and resubmit to the cluster,
118
- # this kills the 1 extra pod
119
- rcs = Keel::GCloud::Kubernetes::ReplicationController.fetch_all deploy_env, app
120
- rc = rcs.first
121
- rc.decrement_replica_count
122
- rc.update
127
+ # Bring the replica count down and resubmit to the cluster,
128
+ # this kills the 1 extra pod
129
+ rcs = Keel::GCloud::Kubernetes::ReplicationController.fetch_all deploy_env, app
130
+ rc = rcs.first
131
+ rc.decrement_replica_count
132
+ rc.update
133
+ end
123
134
  end
124
135
  prompter.print 'Notifying NewRelic of deployment', :info
125
136
  notifier = Keel::Notifier::NewRelic.new env: deploy_env, sha: deploy_sha
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: keel
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Youssef Chaker
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-08-17 00:00:00.000000000 Z
11
+ date: 2017-02-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -52,6 +52,34 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '5.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: guard-minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: guard
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '2.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '2.0'
55
83
  - !ruby/object:Gem::Dependency
56
84
  name: inquirer
57
85
  requirement: !ruby/object:Gem::Requirement
@@ -119,6 +147,7 @@ files:
119
147
  - lib/keel/gcloud/kubernetes/deployment.rb
120
148
  - lib/keel/gcloud/kubernetes/namespace.rb
121
149
  - lib/keel/gcloud/kubernetes/pod.rb
150
+ - lib/keel/gcloud/kubernetes/pod_manager.rb
122
151
  - lib/keel/gcloud/kubernetes/replication_controller.rb
123
152
  - lib/keel/gcloud/prompter.rb
124
153
  - lib/keel/notifier.rb