shipit-engine 0.13.0 → 0.14.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.
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