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 +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
|