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 +4 -4
- data/README.md +17 -0
- data/app/assets/stylesheets/_pages/_deploy.scss +1 -1
- data/app/assets/stylesheets/_structure/_layout.scss +0 -1
- data/app/models/shipit/deploy_spec/bundler_discovery.rb +6 -6
- data/app/models/shipit/stack.rb +2 -0
- data/app/models/shipit/variable_definition.rb +3 -1
- data/app/views/shipit/_variables.html.erb +5 -1
- data/lib/shipit/version.rb +1 -1
- data/lib/snippets/deploy-to-gke +162 -0
- data/test/dummy/app/views/layouts/application.html.erb +1 -0
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/models/task_definitions_test.rb +2 -2
- data/test/unit/deploy_spec_test.rb +17 -6
- data/test/unit/variable_definition_test.rb +59 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 80b89004571af2792321c0d57a3659ebb7cb21aa
|
4
|
+
data.tar.gz: 06363fdf4d7f9e3ba64bf992eaf11855ecfb1061
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
@@ -12,8 +12,11 @@ module Shipit
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def bundle_exec(command)
|
15
|
-
|
16
|
-
|
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
|
-
|
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
|
data/app/models/shipit/stack.rb
CHANGED
@@ -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
|
-
|
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 %>
|
data/lib/shipit/version.rb
CHANGED
@@ -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 %>
|
data/test/dummy/db/test.sqlite3
CHANGED
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.
|
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-
|
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
|