shipit-engine 0.13.0 → 0.14.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: 982c05d52c22af2e2e26f352efc805d9ef961759
4
- data.tar.gz: 3a43a97130cc13814c6368ec89e219cb5face2c9
3
+ metadata.gz: 80b89004571af2792321c0d57a3659ebb7cb21aa
4
+ data.tar.gz: 06363fdf4d7f9e3ba64bf992eaf11855ecfb1061
5
5
  SHA512:
6
- metadata.gz: 2579172befb2eb5054a42935b8bf4fd97bfc53098291468a881171faa708b10f538c3ddbedc2b0560dd0a597bc9f64cee2066d2045ba46d9d66d60e152c38d13
7
- data.tar.gz: 72f2f25e93719769e1ceeffe3c952cbd460a80864151ba9a5761b813114791aadbc6888cebd48e24fb552384ce3943250d698f2ef195ed1c3f304e5bd10d206c
6
+ metadata.gz: ea768d3e022264786c9ddf0b9bbc1d36fb5f8379fc362c9a285d4eab28014d17c248ea9471ff55b0ef45dafa7ac529858e632ee2c2b99aa96cb1f0f1e1d5a00a
7
+ data.tar.gz: 26c9579f88a71172c84c510172d882c31a2edac8b468ef1c983ab056b5a37ff367331a79524172321abd2cecfa19b89a88c40e1784a25600de713f156d3e4009
data/README.md CHANGED
@@ -250,6 +250,23 @@ deploy:
250
250
  ```
251
251
  <br>
252
252
 
253
+ **<code>deploy.variables.select</code>** will turn the input into a `<select>` of values.
254
+
255
+ For example:
256
+
257
+ ```yaml
258
+ deploy:
259
+ variables:
260
+ -
261
+ name: REGION
262
+ title: Run a deploy in a given region
263
+ select:
264
+ - east
265
+ - west
266
+ - north
267
+ ```
268
+ <br>
269
+
253
270
  **<code>deploy.max_commits</code>** allows you to set a limit to the number of commits being shipped per deploys.
254
271
 
255
272
  Human users will be warned that they are not respecting the recommendation, but allowed to continue.
@@ -15,7 +15,7 @@
15
15
  }
16
16
 
17
17
  .variables-fields {
18
- input {
18
+ input, select {
19
19
  display: inline-block;
20
20
  width: inherit;
21
21
  margin-right: 1rem;
@@ -14,7 +14,6 @@
14
14
  // -----------------------------------------------------------------------------
15
15
 
16
16
  .header {
17
- height: 9rem;
18
17
  border-bottom: 1px solid #e5e5e5;
19
18
  background-color: #fff;
20
19
  color: #999;
@@ -12,8 +12,11 @@ module Shipit
12
12
  end
13
13
 
14
14
  def bundle_exec(command)
15
- return command unless bundler?
16
- "bundle exec #{command}"
15
+ if bundler? && dependencies_steps.include?(remove_ruby_version_from_gemfile)
16
+ "bundle exec #{command}"
17
+ else
18
+ command
19
+ end
17
20
  end
18
21
 
19
22
  def bundle_install
@@ -51,10 +54,7 @@ module Shipit
51
54
  end
52
55
 
53
56
  def coerce_task_definition(config)
54
- return super unless bundler?
55
- config['steps'] ||= []
56
- config['steps'] = config['steps'].map(&method(:bundle_exec))
57
- config
57
+ config.merge('steps' => Array(config['steps']).map(&method(:bundle_exec)))
58
58
  end
59
59
  end
60
60
  end
@@ -284,6 +284,8 @@ module Shipit
284
284
  handle_github_redirections do
285
285
  Shipit.github_api.commits(github_repo_name, sha: branch)
286
286
  end
287
+ rescue Octokit::Conflict
288
+ [] # Repository is empty...
287
289
  end
288
290
 
289
291
  def handle_github_redirections
@@ -1,11 +1,12 @@
1
1
  module Shipit
2
2
  class VariableDefinition
3
- attr_reader :name, :title, :default
3
+ attr_reader :name, :title, :default, :select
4
4
 
5
5
  def initialize(attributes)
6
6
  @name = attributes.fetch('name')
7
7
  @title = attributes['title']
8
8
  @default = attributes['default'].to_s
9
+ @select = attributes['select'].presence
9
10
  end
10
11
 
11
12
  def to_h
@@ -13,6 +14,7 @@ module Shipit
13
14
  'name' => @name,
14
15
  'title' => @title,
15
16
  'default' => @default,
17
+ 'select' => @select,
16
18
  }
17
19
  end
18
20
  end
@@ -6,7 +6,11 @@
6
6
  <%= form.fields_for field_name do |field| %>
7
7
  <% variables.each do |variable| %>
8
8
  <p class="variables-fields">
9
- <%= field.text_field variable.name, value: variable.default %>
9
+ <% if variable.select %>
10
+ <%= field.select variable.name, options_for_select([["Please select...", { disabled: "disabled", selected: true }]] + variable.select) %>
11
+ <% else %>
12
+ <%= field.text_field variable.name, value: variable.default %>
13
+ <% end %>
10
14
  <%= field.label variable.name, variable.title || variable.name %>
11
15
  </p>
12
16
  <% end %>
@@ -1,3 +1,3 @@
1
1
  module Shipit
2
- VERSION = '0.13.0'.freeze
2
+ VERSION = '0.14.0'.freeze
3
3
  end
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Usage: deploy-to-gke <app's namespace> <gcloud deployment> <gcloud project>
4
+
5
+ # Requires the following to be available on Shipit machine:
6
+ # - gcloud binary available on the shipit machine's path
7
+ # - kubectl binary available in the shipit machine's path
8
+ # - $GCLOUD_CREDENTIALS_DIR/my-gcloud-project-name.json must exist
9
+
10
+ # Optionally, the following variables can be used to override script defaults:
11
+ # - K8S_TEMPLATE_FOLDER: location of Kubernetes files to deploy. Default is config/deploy/#{environment}.
12
+
13
+ require 'open3'
14
+ require 'securerandom'
15
+ require 'erb'
16
+ require 'json'
17
+ require 'yaml'
18
+ require 'shellwords'
19
+ require 'tempfile'
20
+
21
+ class GKEDeployment
22
+ class FatalDeploymentError < StandardError; end
23
+
24
+ def initialize(namespace:, gcloud_deployment:, gcloud_project:, environment:, current_sha:, key_dir:, template_folder: nil)
25
+ @namespace = namespace
26
+ @gcloud_deployment = gcloud_deployment
27
+ @gcloud_project = gcloud_project
28
+ @current_sha = current_sha
29
+ @key_file = "#{key_dir}/#{@gcloud_project}.json"
30
+ @template_path = './' + (template_folder || "config/deploy/#{environment}")
31
+
32
+ # Validate params + check existance of auth key and template(s)
33
+ enforce_required_params
34
+ preliminary_check
35
+
36
+ # Max length of podname is only 63chars so try to save some room by truncating sha to 8 chars
37
+ @id = current_sha[0...8] + "-#{SecureRandom.hex(4)}"
38
+ end
39
+
40
+ def run
41
+ authorize_gcloud
42
+ fetch_clusters.each do |cluster|
43
+ set_kubectl_cluster(cluster)
44
+ apply_all_templates
45
+ end
46
+ rescue FatalDeploymentError => error
47
+ print_error(error.message)
48
+ exit 1
49
+ end
50
+
51
+ def template_variables
52
+ {
53
+ 'current_sha' => @current_sha,
54
+ 'deployment_id' => @id,
55
+ }
56
+ end
57
+
58
+ private
59
+
60
+ def enforce_required_params
61
+ [@namespace, @gcloud_project, @gcloud_deployment, @current_sha, @key_file].each do |required_param|
62
+ raise ArgumentError, "#{required_param} is required" unless required_param && !required_param.empty?
63
+ end
64
+ end
65
+
66
+ def preliminary_check
67
+ raise FatalDeploymentError, "Project config missing at #{@key_file}" unless File.file?(@key_file)
68
+ raise FatalDeploymentError, "#{@template_path} doesn't exist" unless File.directory?(@template_path)
69
+ raise FatalDeploymentError, "#{@template_path} doesn't have files with postfix .yml or .yml.erb" unless Dir.entries(@template_path).select {|file| file =~ /\.yml(.erb)?$/}.size > 0
70
+ end
71
+
72
+ def apply_all_templates
73
+ found = 0
74
+ Dir.foreach(@template_path) do |file|
75
+ file_path = "#{@template_path}/#{file}"
76
+ if File.extname(file) == '.yml'
77
+ found += 1
78
+ apply_template(file_path)
79
+ elsif File.extname(file) == '.erb'
80
+ found += 1
81
+ render_and_apply_template(file_path)
82
+ end
83
+ end
84
+ raise FatalDeploymentError, "No templates found in #{@template_path}" if found.zero?
85
+ end
86
+
87
+ def render_and_apply_template(file_path)
88
+ erb_template = ERB.new(File.read(file_path))
89
+ erb_binding = binding
90
+ template_variables.each do |var_name, value|
91
+ erb_binding.local_variable_set(var_name, value)
92
+ end
93
+ content = erb_template.result(erb_binding)
94
+
95
+ f = Tempfile.new(['kube_template', '.yml'])
96
+ f.write(content)
97
+ f.close
98
+ apply_template(f.path, original_path: file_path)
99
+ ensure
100
+ f.unlink if f
101
+ end
102
+
103
+ def apply_template(path, original_path: nil)
104
+ status = run_command('kubectl', 'apply', '-f', path, "--namespace=#{@namespace}")
105
+ raise FatalDeploymentError, "Failed to apply template #{original_path || path}" unless status
106
+ end
107
+
108
+ def authorize_gcloud
109
+ ENV['GOOGLE_APPLICATION_CREDENTIALS'] = @key_file
110
+ status = run_command('gcloud', '-q', 'auth', 'activate-service-account', '--key-file', @key_file)
111
+ status = run_command('gcloud', '-q', 'config', 'set', 'project', @gcloud_project) if status
112
+ raise FatalDeploymentError, "Failed to set gcloud project #{@gcloud_project}" unless status
113
+ end
114
+
115
+ def set_kubectl_cluster(cluster)
116
+ cluster_name = cluster[0]
117
+ cluster_zone = cluster[1]
118
+ status = run_command('gcloud', '-q', 'container', 'clusters', 'get-credentials', cluster_name, '--zone', cluster_zone)
119
+ raise FatalDeploymentError, "Failed to set cluster #{cluster_name}/#{cluster_zone}" unless status
120
+ end
121
+
122
+ def fetch_clusters
123
+ result = query_deployments
124
+ result['resources'].each_with_object([]) do |resource, data|
125
+ next unless resource['type'] == 'container.v1.cluster'
126
+ properties = YAML.load(resource['finalProperties'])
127
+ data << [resource['name'], properties['zone']]
128
+ end
129
+ end
130
+
131
+ def run_command(*args)
132
+ puts Shellwords.join(args)
133
+ out, err, st = Open3.capture3(*args)
134
+ puts out
135
+ print_error(err) unless st.success?
136
+ st.success?
137
+ end
138
+
139
+ def print_error(msg)
140
+ puts "\033[0;31m#{msg}\033[0m"
141
+ end
142
+
143
+ def query_deployments
144
+ out, err, st = Open3.capture3('gcloud', '-q', 'deployment-manager', 'deployments', 'describe', @gcloud_deployment, '--format=json')
145
+ unless st.success?
146
+ print_error(err)
147
+ raise FatalDeploymentError, "Failed to fetch cluster with deployment #{@gcloud_deployment}"
148
+ end
149
+ JSON.parse(out)
150
+ end
151
+ end
152
+
153
+ deployment = GKEDeployment.new(
154
+ namespace: ARGV[0],
155
+ gcloud_deployment: ARGV[1],
156
+ gcloud_project: ARGV[2],
157
+ environment: ENV['ENVIRONMENT'],
158
+ template_folder: ENV['K8S_TEMPLATE_FOLDER'],
159
+ current_sha: ENV['REVISION'],
160
+ key_dir: ENV['GCLOUD_CREDENTIALS_DIR']
161
+ )
162
+ deployment.run
@@ -2,6 +2,7 @@
2
2
  <html>
3
3
  <head>
4
4
  <title>Shipit</title>
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
5
6
  <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
6
7
  <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
7
8
  <%= csrf_meta_tags %>
Binary file
@@ -34,8 +34,8 @@ module Shipit
34
34
  checklist: [],
35
35
  allow_concurrency: true,
36
36
  variables: [
37
- {'name' => 'FOO', 'title' => 'Set to 0 to foo', 'default' => '1'},
38
- {'name' => 'BAR', 'title' => 'Set to 1 to bar', 'default' => '0'},
37
+ {'name' => 'FOO', 'title' => 'Set to 0 to foo', 'default' => '1', 'select' => nil},
38
+ {'name' => 'BAR', 'title' => 'Set to 1 to bar', 'default' => '0', 'select' => nil},
39
39
  ],
40
40
  }
41
41
  assert_equal as_json, TaskDefinition.load(TaskDefinition.dump(@definition)).as_json
@@ -37,14 +37,14 @@ module Shipit
37
37
  end
38
38
 
39
39
  test '#dependencies_steps returns `bundle install` if a `Gemfile` is present' do
40
- @spec.expects(:bundler?).returns(true)
40
+ @spec.expects(:bundler?).returns(true).at_least_once
41
41
  @spec.expects(:bundle_install).returns(['bundle install'])
42
42
  assert_equal ['bundle install'], @spec.dependencies_steps
43
43
  end
44
44
 
45
45
  test "#dependencies_steps prepend and append pre and post steps" do
46
46
  @spec.stubs(:load_config).returns('dependencies' => {'pre' => ['before'], 'post' => ['after']})
47
- @spec.expects(:bundler?).returns(true)
47
+ @spec.expects(:bundler?).returns(true).at_least_once
48
48
  @spec.expects(:bundle_install).returns(['bundle install'])
49
49
  assert_equal ['before', 'bundle install', 'after'], @spec.dependencies_steps
50
50
  end
@@ -111,14 +111,14 @@ module Shipit
111
111
  end
112
112
 
113
113
  test '#deploy_steps returns `cap $ENVIRONMENT deploy` if a `Capfile` is present' do
114
- @spec.expects(:bundler?).returns(true)
114
+ @spec.expects(:bundler?).returns(true).at_least_once
115
115
  @spec.expects(:capistrano?).returns(true)
116
116
  assert_equal ['bundle exec cap $ENVIRONMENT deploy'], @spec.deploy_steps
117
117
  end
118
118
 
119
119
  test "#deploy_steps prepend and append pre and post steps" do
120
120
  @spec.stubs(:load_config).returns('deploy' => {'pre' => ['before'], 'post' => ['after']})
121
- @spec.expects(:bundler?).returns(true)
121
+ @spec.expects(:bundler?).returns(true).at_least_once
122
122
  @spec.expects(:capistrano?).returns(true)
123
123
  assert_equal ['before', 'bundle exec cap $ENVIRONMENT deploy', 'after'], @spec.deploy_steps
124
124
  end
@@ -136,14 +136,14 @@ module Shipit
136
136
  end
137
137
 
138
138
  test '#rollback_steps returns `cap $ENVIRONMENT deploy:rollback` if a `Capfile` is present' do
139
- @spec.expects(:bundler?).returns(true)
139
+ @spec.expects(:bundler?).returns(true).at_least_once
140
140
  @spec.expects(:capistrano?).returns(true)
141
141
  assert_equal ['bundle exec cap $ENVIRONMENT deploy:rollback'], @spec.rollback_steps
142
142
  end
143
143
 
144
144
  test "#rollback_steps prepend and append pre and post steps" do
145
145
  @spec.stubs(:load_config).returns('rollback' => {'pre' => ['before'], 'post' => ['after']})
146
- @spec.expects(:bundler?).returns(true)
146
+ @spec.expects(:bundler?).returns(true).at_least_once
147
147
  @spec.expects(:capistrano?).returns(true)
148
148
  assert_equal ['before', 'bundle exec cap $ENVIRONMENT deploy:rollback', 'after'], @spec.rollback_steps
149
149
  end
@@ -262,6 +262,17 @@ module Shipit
262
262
  assert_equal ['bundle exec foo'], definition.steps
263
263
  end
264
264
 
265
+ test "task definitions do not prepend bundle exec if depedency step is overridden" do
266
+ @spec.expects(:load_config).returns(
267
+ 'dependencies' => {'override' => []},
268
+ 'tasks' => {'restart' => {'steps' => %w(foo)}},
269
+ )
270
+ @spec.expects(:bundler?).returns(true).at_least_once
271
+ definition = @spec.find_task_definition('restart')
272
+
273
+ assert_equal ['foo'], definition.steps
274
+ end
275
+
265
276
  test "task definitions prepend bundle exec before serialization" do
266
277
  @spec.expects(:load_config).returns('tasks' => {'restart' => {'steps' => %w(foo)}})
267
278
  @spec.expects(:bundler?).returns(true).at_least_once
@@ -0,0 +1,59 @@
1
+ require 'test_helper'
2
+
3
+ module Shipit
4
+ class VariableDefinitionTest < ActiveSupport::TestCase
5
+ setup do
6
+ @attributes = {
7
+ "name" => "Variable name",
8
+ "title" => "Variable title",
9
+ "default" => "Variable default",
10
+ }
11
+ @select = %w(var1 var2 var3)
12
+ end
13
+
14
+ test "#initialize sets up the expected values" do
15
+ subject = Shipit::VariableDefinition.new(@attributes)
16
+
17
+ assert_equal "Variable name", subject.name
18
+ assert_equal "Variable title", subject.title
19
+ assert_equal "Variable default", subject.default
20
+ assert_nil subject.select
21
+ end
22
+
23
+ test "#initialize name is required" do
24
+ assert_raises KeyError do
25
+ @attributes.delete("name")
26
+ Shipit::VariableDefinition.new(@attributes)
27
+ end
28
+ end
29
+
30
+ test "#initialize stringifies the default" do
31
+ @attributes["default"] = :value
32
+ subject = Shipit::VariableDefinition.new(@attributes)
33
+
34
+ assert_equal "value", subject.default
35
+ end
36
+
37
+ test "#initialize sets the select if present" do
38
+ @attributes["select"] = @select
39
+ subject = Shipit::VariableDefinition.new(@attributes)
40
+
41
+ assert_equal @select, subject.select
42
+ end
43
+
44
+ test "#initialize sets nil for select if the array is empty" do
45
+ @attributes["select"] = []
46
+ subject = Shipit::VariableDefinition.new(@attributes)
47
+
48
+ assert_nil subject.select
49
+ end
50
+
51
+ test "#to_h returns hash version" do
52
+ assert_equal @attributes.merge("select" => nil), Shipit::VariableDefinition.new(@attributes).to_h
53
+ end
54
+
55
+ test "#to_h returns hash version that includes select" do
56
+ assert_equal @attributes.merge("select" => @select), Shipit::VariableDefinition.new(@attributes.merge("select" => @select)).to_h
57
+ end
58
+ end
59
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shipit-engine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.0
4
+ version: 0.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jean Boussier
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-11-03 00:00:00.000000000 Z
11
+ date: 2016-11-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -737,6 +737,7 @@ files:
737
737
  - lib/shipit/version.rb
738
738
  - lib/snippets/assert-egg-version-tag
739
739
  - lib/snippets/assert-gem-version-tag
740
+ - lib/snippets/deploy-to-gke
740
741
  - lib/snippets/extract-egg-version
741
742
  - lib/snippets/extract-gem-version
742
743
  - lib/snippets/fetch-gem-version
@@ -883,6 +884,7 @@ files:
883
884
  - test/unit/environment_variables_test.rb
884
885
  - test/unit/github_url_helper_test.rb
885
886
  - test/unit/shipit_test.rb
887
+ - test/unit/variable_definition_test.rb
886
888
  - vendor/assets/javascripts/clusterize.js
887
889
  - vendor/assets/javascripts/jquery-notify.js
888
890
  - vendor/assets/javascripts/mousetrap-global-bind.js
@@ -1051,3 +1053,4 @@ test_files:
1051
1053
  - test/unit/environment_variables_test.rb
1052
1054
  - test/unit/github_url_helper_test.rb
1053
1055
  - test/unit/shipit_test.rb
1056
+ - test/unit/variable_definition_test.rb