hippo-cli 1.0.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 +7 -0
- checksums.yaml.gz.sig +3 -0
- data/bin/hippo +36 -0
- data/cli/apply_config.rb +17 -0
- data/cli/apply_services.rb +15 -0
- data/cli/build.rb +16 -0
- data/cli/console.rb +31 -0
- data/cli/deploy.rb +45 -0
- data/cli/edit-secret.rb +45 -0
- data/cli/help.rb +21 -0
- data/cli/init.rb +25 -0
- data/cli/install.rb +44 -0
- data/cli/kubectl.rb +16 -0
- data/cli/objects.rb +34 -0
- data/cli/publish.rb +17 -0
- data/cli/secrets.rb +23 -0
- data/cli/status.rb +15 -0
- data/lib/hippo/build_spec.rb +32 -0
- data/lib/hippo/cli_steps.rb +274 -0
- data/lib/hippo/error.rb +18 -0
- data/lib/hippo/kubernetes.rb +200 -0
- data/lib/hippo/recipe.rb +126 -0
- data/lib/hippo/repository.rb +122 -0
- data/lib/hippo/secret.rb +165 -0
- data/lib/hippo/secret_manager.rb +99 -0
- data/lib/hippo/stage.rb +34 -0
- data/lib/hippo/util.rb +50 -0
- data/lib/hippo/version.rb +3 -0
- data/lib/hippo/yaml_part.rb +47 -0
- data/lib/hippo.rb +7 -0
- data/template/Hippofile +19 -0
- data/template/config/production/env-vars.yaml +7 -0
- data/template/deployments/web.yaml +29 -0
- data/template/deployments/worker.yaml +26 -0
- data/template/jobs/install/load-schema.yaml +22 -0
- data/template/jobs/upgrade/db-migration.yaml +22 -0
- data/template/services/main.ingress.yaml +13 -0
- data/template/services/web.svc.yaml +11 -0
- data/template/stages/production.yaml +6 -0
- data.tar.gz.sig +0 -0
- metadata +163 -0
- metadata.gz.sig +1 -0
@@ -0,0 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'digest'
|
4
|
+
require 'git'
|
5
|
+
require 'hippo/error'
|
6
|
+
|
7
|
+
module Hippo
|
8
|
+
class Repository
|
9
|
+
def initialize(options)
|
10
|
+
@options = options
|
11
|
+
end
|
12
|
+
|
13
|
+
def url
|
14
|
+
@options['url']
|
15
|
+
end
|
16
|
+
|
17
|
+
# Return the path where this repository is stored on the local
|
18
|
+
# computer.
|
19
|
+
#
|
20
|
+
# @return [String]
|
21
|
+
def path
|
22
|
+
return @options['path'] if @options['path']
|
23
|
+
|
24
|
+
@path ||= begin
|
25
|
+
digest = Digest::SHA256.hexdigest(url)
|
26
|
+
File.join('', 'tmp', 'hippo-repos', digest)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Clone this repository into the working directory for this
|
31
|
+
# application.
|
32
|
+
#
|
33
|
+
# @return [Boolean]
|
34
|
+
def clone
|
35
|
+
if File.directory?(path)
|
36
|
+
raise RepositoryAlreadyClonedError, "Repository has already been cloned to #{path}. Maybe you just want to pull?"
|
37
|
+
end
|
38
|
+
|
39
|
+
@git = Git.clone(url, path)
|
40
|
+
true
|
41
|
+
rescue Git::GitExecuteError => e
|
42
|
+
raise RepositoryCloneError, e.message
|
43
|
+
end
|
44
|
+
|
45
|
+
# Has this been cloned?
|
46
|
+
#
|
47
|
+
# @return [Boolean]
|
48
|
+
def cloned?
|
49
|
+
File.directory?(path)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Fetch the latest copy of this repository
|
53
|
+
#
|
54
|
+
# @return [Boolean]
|
55
|
+
def fetch
|
56
|
+
git.fetch
|
57
|
+
true
|
58
|
+
rescue Git::GitExecuteError => e
|
59
|
+
raise RepositoryFetchError, e.message
|
60
|
+
end
|
61
|
+
|
62
|
+
# Checkout the version of the application for the given commit or
|
63
|
+
# branch name in the local copy.
|
64
|
+
#
|
65
|
+
# @param ref [String]
|
66
|
+
# @return [Boolean]
|
67
|
+
def checkout(ref)
|
68
|
+
git.checkout("origin/#{ref}")
|
69
|
+
true
|
70
|
+
rescue Git::GitExecuteError => e
|
71
|
+
if e.message =~ /did not match any file\(s\) known to git/
|
72
|
+
raise RepositoryCheckoutError, "No branch named '#{ref}' found in repository"
|
73
|
+
else
|
74
|
+
raise RepositoryCheckoutError, e.message
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Return the commit reference for the currently checked out branch
|
79
|
+
#
|
80
|
+
# @return [String]
|
81
|
+
def commit
|
82
|
+
git.log(1).first
|
83
|
+
end
|
84
|
+
|
85
|
+
# Get the commit reference for the given branch on the remote
|
86
|
+
# repository by asking it directly.
|
87
|
+
#
|
88
|
+
# @param name [String]
|
89
|
+
# @return [Git::Commit]
|
90
|
+
def commit_for_branch(branch)
|
91
|
+
git.object("origin/#{branch}").log(1).first
|
92
|
+
rescue Git::GitExecuteError => e
|
93
|
+
if e.message =~ /Not a valid object name/
|
94
|
+
raise Error, "'#{branch}' is not a valid branch name in repository"
|
95
|
+
else
|
96
|
+
raise
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Return the template variables for a repository
|
101
|
+
#
|
102
|
+
# @return [Hash]
|
103
|
+
def template_vars
|
104
|
+
{
|
105
|
+
'url' => url,
|
106
|
+
'path' => path
|
107
|
+
}
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def git
|
113
|
+
@git ||= begin
|
114
|
+
if cloned?
|
115
|
+
Git.open(path)
|
116
|
+
else
|
117
|
+
raise Error, 'Could not create a git instance because there is no repository cloned for it.'
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
data/lib/hippo/secret.rb
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'securerandom'
|
4
|
+
require 'openssl'
|
5
|
+
require 'encryptor'
|
6
|
+
require 'hippo/util'
|
7
|
+
|
8
|
+
module Hippo
|
9
|
+
class Secret
|
10
|
+
include Hippo::Util
|
11
|
+
|
12
|
+
HEADER = [
|
13
|
+
'# This file is encrypted and managed by Hippo.',
|
14
|
+
'# Use `hippo secret [stage] [name]` to make changes to it.',
|
15
|
+
'#',
|
16
|
+
'# Note: this cannot be applied directly to your Kubernetes server because',
|
17
|
+
'# HippoEncryptedSecret is not a valid object. It will be automatically ',
|
18
|
+
'# converted to a Secret when it is applied by Hippo.'
|
19
|
+
].join("\n")
|
20
|
+
|
21
|
+
def initialize(manager, name)
|
22
|
+
@manager = manager
|
23
|
+
@name = name
|
24
|
+
end
|
25
|
+
|
26
|
+
# Return the path to the stored encrypted secret file
|
27
|
+
#
|
28
|
+
# @return [String]
|
29
|
+
def path
|
30
|
+
File.join(@manager.recipe.root, 'secrets', @manager.stage.name, "#{@name}.yaml")
|
31
|
+
end
|
32
|
+
|
33
|
+
# Does the secret file currently exist on the file system?
|
34
|
+
#
|
35
|
+
# @return [Boolean]
|
36
|
+
def exists?
|
37
|
+
File.file?(path)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Return the secret file as it should be applied to Kubernetes.
|
41
|
+
#
|
42
|
+
# @return [String]
|
43
|
+
def to_secret_yaml
|
44
|
+
decrypted_parts.map do |part, _array|
|
45
|
+
part['kind'] = 'Secret'
|
46
|
+
part['metadata'] ||= {}
|
47
|
+
part['metadata']['namespace'] = @manager.stage.namespace
|
48
|
+
|
49
|
+
part['data'].each do |key, value|
|
50
|
+
part['data'][key] = Base64.encode64(value).gsub("\n", '').strip
|
51
|
+
end
|
52
|
+
part
|
53
|
+
end.map { |p| p.hash.to_yaml } .join("---\n")
|
54
|
+
end
|
55
|
+
|
56
|
+
# Return the secret file as it should be displayed for editting
|
57
|
+
#
|
58
|
+
# @return [String]
|
59
|
+
def to_editable_yaml
|
60
|
+
decrypted_parts.map { |p| p.hash.to_yaml } .join("---\n")
|
61
|
+
end
|
62
|
+
|
63
|
+
# Edit a secret file by opening an editor and allow changes to be made.
|
64
|
+
# When the editor completes, finish by writing the file back to the disk.
|
65
|
+
#
|
66
|
+
# @return [void]
|
67
|
+
def edit
|
68
|
+
return unless exists?
|
69
|
+
|
70
|
+
# Obtain a list of parts and map them to the name of the secret
|
71
|
+
# in the file.
|
72
|
+
original_decrypted_part_data = parse_parts(load_yaml_from_path(path), :decrypt)
|
73
|
+
original_encrypted_part_data = load_yaml_from_path(path).each_with_object({}) do |part, hash|
|
74
|
+
next if part.nil? || part.empty?
|
75
|
+
|
76
|
+
hash[part.dig('metadata', 'name')] = part['data']
|
77
|
+
end
|
78
|
+
original_part_data = original_decrypted_part_data.each_with_object({}) do |part, hash|
|
79
|
+
name = part.dig('metadata', 'name')
|
80
|
+
enc = original_encrypted_part_data[name]
|
81
|
+
next if enc.nil?
|
82
|
+
|
83
|
+
hash[part.dig('metadata', 'name')] = part['data'].each_with_object({}) do |(key, value), hash2|
|
84
|
+
hash2[key] = [value, enc[key]]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Open the editor and gather what the user provides.
|
89
|
+
saved_contents = open_in_editor("secret-#{@name}", to_editable_yaml)
|
90
|
+
|
91
|
+
# This saved contents should now be validated to ensure it is valid
|
92
|
+
# YAML and, if so, it should be encrypted and then saved into the
|
93
|
+
# secret file as needed.
|
94
|
+
begin
|
95
|
+
yaml_parts = load_yaml_from_data(saved_contents)
|
96
|
+
parts = parse_parts(yaml_parts, :encrypt, original_part_data)
|
97
|
+
write(parts)
|
98
|
+
rescue StandardError => e
|
99
|
+
raise
|
100
|
+
puts "An error occurred parsing your file: #{e.message}"
|
101
|
+
saved_contents = open_in_editor("secret-#{@name}", saved_contents)
|
102
|
+
retry
|
103
|
+
end
|
104
|
+
|
105
|
+
puts "#{@name} secret has been editted"
|
106
|
+
end
|
107
|
+
|
108
|
+
# Create a new templated encrypted secret with the given name
|
109
|
+
#
|
110
|
+
# @return [void]
|
111
|
+
def create
|
112
|
+
template = {
|
113
|
+
'apiVersion' => 'v1',
|
114
|
+
'kind' => 'HippoEncryptedSecret',
|
115
|
+
'metadata' => {
|
116
|
+
'name' => @name
|
117
|
+
},
|
118
|
+
'data' => {
|
119
|
+
'example' => @manager.encrypt('This is an example secret!')
|
120
|
+
}
|
121
|
+
}
|
122
|
+
write([template])
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
def decrypted_parts
|
128
|
+
return unless exists?
|
129
|
+
|
130
|
+
yaml_parts = load_yaml_from_path(path)
|
131
|
+
parse_parts(yaml_parts, :decrypt)
|
132
|
+
end
|
133
|
+
|
134
|
+
def parse_parts(yaml_parts, method, skips = {})
|
135
|
+
yaml_parts.each_with_object([]) do |part, array|
|
136
|
+
next if part.hash.nil? || part.hash.empty?
|
137
|
+
|
138
|
+
part['data'].each do |key, value|
|
139
|
+
skip = skips[part.dig('metadata', 'name')]
|
140
|
+
part['data'][key] = if skip && skip[key] && skip[key][0] == value
|
141
|
+
skip[key][1]
|
142
|
+
else
|
143
|
+
@manager.public_send(method, value.to_s)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
array << part
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Write the array of given parts into a file along with a suitable
|
152
|
+
# explanatory header.
|
153
|
+
#
|
154
|
+
# @return [void]
|
155
|
+
def write(parts)
|
156
|
+
parts = parts.map(&:to_yaml).join("---\n")
|
157
|
+
data_to_write = HEADER + "\n" + parts
|
158
|
+
|
159
|
+
FileUtils.mkdir_p(File.dirname(path))
|
160
|
+
File.open(path, 'w') do |f|
|
161
|
+
f.write(data_to_write)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'base64'
|
4
|
+
require 'hippo/secret'
|
5
|
+
|
6
|
+
module Hippo
|
7
|
+
class SecretManager
|
8
|
+
attr_reader :recipe
|
9
|
+
attr_reader :stage
|
10
|
+
attr_reader :key
|
11
|
+
|
12
|
+
CIPHER = OpenSSL::Cipher.new('aes-256-gcm')
|
13
|
+
|
14
|
+
def initialize(recipe, stage)
|
15
|
+
@recipe = recipe
|
16
|
+
@stage = stage
|
17
|
+
end
|
18
|
+
|
19
|
+
# Generate and publish a new secret key to the Kubernetes API.
|
20
|
+
#
|
21
|
+
# @return [void]
|
22
|
+
def create_key
|
23
|
+
if key_available?
|
24
|
+
raise Hippo::Error, 'A key already exists on Kubernetes. Remove this first.'
|
25
|
+
end
|
26
|
+
|
27
|
+
CIPHER.encrypt
|
28
|
+
secret_key = CIPHER.random_key
|
29
|
+
secret_key64 = Base64.encode64(secret_key).gsub("\n", '').strip
|
30
|
+
object = {
|
31
|
+
'apiVersion' => 'v1',
|
32
|
+
'kind' => 'Secret',
|
33
|
+
'type' => 'hippo.adam.ac/secret-encryption-key',
|
34
|
+
'metadata' => { 'name' => 'hippo-secret-key', 'namespace' => @stage.namespace },
|
35
|
+
'data' => { 'key' => Base64.encode64(secret_key64).gsub("\n", '').strip }
|
36
|
+
}
|
37
|
+
@recipe.kubernetes.apply_with_kubectl(object.to_yaml)
|
38
|
+
@key = secret_key
|
39
|
+
end
|
40
|
+
|
41
|
+
# Download the current key from the Kubernetes API and set it as the
|
42
|
+
# key for this instance
|
43
|
+
#
|
44
|
+
# @return [void]
|
45
|
+
def download_key
|
46
|
+
return if @key
|
47
|
+
|
48
|
+
value = @recipe.kubernetes.get_with_kubectl(@stage, 'secret', 'hippo-secret-key').first
|
49
|
+
return if value.nil?
|
50
|
+
return if value.dig('data', 'key').nil?
|
51
|
+
|
52
|
+
@key = Base64.decode64(Base64.decode64(value['data']['key']))
|
53
|
+
rescue Hippo::Error => e
|
54
|
+
raise unless e.message =~ /not found/
|
55
|
+
end
|
56
|
+
|
57
|
+
def key_available?
|
58
|
+
download_key
|
59
|
+
!@key.nil?
|
60
|
+
end
|
61
|
+
|
62
|
+
def secret(name)
|
63
|
+
Secret.new(self, name)
|
64
|
+
end
|
65
|
+
|
66
|
+
def secrets
|
67
|
+
Dir[File.join('secrets', @stage.name, '**', '*.{yml,yaml}')].map do |path|
|
68
|
+
secret(path.split('/').last.sub(/\.ya?ml\z/, ''))
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def encrypt(value)
|
73
|
+
CIPHER.encrypt
|
74
|
+
iv = CIPHER.random_iv
|
75
|
+
salt = SecureRandom.random_bytes(16)
|
76
|
+
encrypted_value = Encryptor.encrypt(value: value.to_s, key: @key, iv: iv, salt: salt)
|
77
|
+
'encrypted:' + Base64.encode64([
|
78
|
+
Base64.encode64(encrypted_value),
|
79
|
+
Base64.encode64(salt),
|
80
|
+
Base64.encode64(iv)
|
81
|
+
].join('---')).gsub("\n", '')
|
82
|
+
end
|
83
|
+
|
84
|
+
# Decrypt the given value value and return it
|
85
|
+
#
|
86
|
+
# @param value [String]
|
87
|
+
# @return [String]
|
88
|
+
def decrypt(value)
|
89
|
+
value = value.to_s
|
90
|
+
if value =~ /\Aencrypted:(.*)/
|
91
|
+
value = Base64.decode64(Regexp.last_match(1))
|
92
|
+
encrypted_value, salt, iv = value.split('---', 3).map { |s| Base64.decode64(s) }
|
93
|
+
Encryptor.decrypt(value: encrypted_value, key: @key, iv: iv, salt: salt).to_s
|
94
|
+
else
|
95
|
+
value
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/lib/hippo/stage.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hippo
|
4
|
+
class Stage
|
5
|
+
def initialize(options)
|
6
|
+
@options = options
|
7
|
+
end
|
8
|
+
|
9
|
+
def name
|
10
|
+
@options['name']
|
11
|
+
end
|
12
|
+
|
13
|
+
def branch
|
14
|
+
@options['branch']
|
15
|
+
end
|
16
|
+
|
17
|
+
def namespace
|
18
|
+
@options['namespace']
|
19
|
+
end
|
20
|
+
|
21
|
+
def template_vars
|
22
|
+
{
|
23
|
+
'name' => name,
|
24
|
+
'branch' => branch,
|
25
|
+
'namespace' => namespace,
|
26
|
+
'vars' => @options['vars'] || {}
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
def kubectl(*command)
|
31
|
+
"kubectl -n #{namespace} #{command.join(' ')}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/hippo/util.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'hippo/error'
|
4
|
+
require 'hippo/yaml_part'
|
5
|
+
|
6
|
+
module Hippo
|
7
|
+
module Util
|
8
|
+
def load_yaml_from_path(path)
|
9
|
+
return nil if path.nil?
|
10
|
+
return nil unless File.file?(path)
|
11
|
+
|
12
|
+
data = File.read(path)
|
13
|
+
load_yaml_from_data(data, path)
|
14
|
+
end
|
15
|
+
|
16
|
+
def load_yaml_from_data(data, path = nil)
|
17
|
+
parts = data.split(/^\-\-\-$/)
|
18
|
+
parts.each_with_index.map do |part, index|
|
19
|
+
YAMLPart.new(part, path, index)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def load_yaml_from_directory(path)
|
24
|
+
return [] if path.nil?
|
25
|
+
return [] unless File.directory?(path)
|
26
|
+
|
27
|
+
Dir[File.join(path, '*.{yaml,yml}')].sort.each_with_object([]) do |path, array|
|
28
|
+
yaml = load_yaml_from_path(path)
|
29
|
+
next if yaml.nil?
|
30
|
+
|
31
|
+
array << yaml
|
32
|
+
end.flatten
|
33
|
+
end
|
34
|
+
|
35
|
+
def open_in_editor(name, contents)
|
36
|
+
tmp_root = File.join(ENV['HOME'], '.hippo')
|
37
|
+
FileUtils.mkdir_p(tmp_root)
|
38
|
+
begin
|
39
|
+
tmpfile = Tempfile.new([name, '.yaml'], tmp_root)
|
40
|
+
tmpfile.write(contents)
|
41
|
+
tmpfile.close
|
42
|
+
system("#{ENV['EDITOR']} #{tmpfile.path}")
|
43
|
+
tmpfile.open
|
44
|
+
tmpfile.read
|
45
|
+
ensure
|
46
|
+
tmpfile.unlink
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hippo
|
4
|
+
class YAMLPart
|
5
|
+
attr_reader :yaml
|
6
|
+
|
7
|
+
def initialize(yaml, path, index)
|
8
|
+
@yaml = yaml.strip
|
9
|
+
@path = path
|
10
|
+
@index = index
|
11
|
+
end
|
12
|
+
|
13
|
+
def hash
|
14
|
+
@hash ||= YAML.safe_load(@yaml)
|
15
|
+
rescue Psych::SyntaxError => e
|
16
|
+
raise Error, "YAML parsing error in #{@path} (index #{@index}) (#{e.message})"
|
17
|
+
end
|
18
|
+
|
19
|
+
def empty?
|
20
|
+
@yaml.nil? ||
|
21
|
+
@yaml.empty? ||
|
22
|
+
hash.nil? ||
|
23
|
+
hash.empty?
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_yaml
|
27
|
+
hash.to_yaml
|
28
|
+
end
|
29
|
+
|
30
|
+
def dig(*args)
|
31
|
+
hash.dig(*args)
|
32
|
+
end
|
33
|
+
|
34
|
+
def [](name)
|
35
|
+
hash[name]
|
36
|
+
end
|
37
|
+
|
38
|
+
def []=(name, value)
|
39
|
+
hash[name] = value
|
40
|
+
end
|
41
|
+
|
42
|
+
def parse(recipe, stage, commit)
|
43
|
+
parsed_part = recipe.parse(stage, commit, @yaml)
|
44
|
+
self.class.new(parsed_part, @path, @index)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/hippo.rb
ADDED
data/template/Hippofile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# Welcome to your Hippofile 🦛
|
2
|
+
#
|
3
|
+
# In here you configure how you wish to build, publish and
|
4
|
+
# deploy your application using Docker & Kubernetes.
|
5
|
+
|
6
|
+
# Where is the application that you wish to deploy hosted?
|
7
|
+
repository:
|
8
|
+
url: git@github.com:username/repo
|
9
|
+
|
10
|
+
# You can request multiple builds from the same repository to be built if
|
11
|
+
# required. In most cases you'll probably only need one. You can reference
|
12
|
+
# these in your Kubernetes objects to specify image details in PodSpecs.
|
13
|
+
builds:
|
14
|
+
app:
|
15
|
+
dockerfile: Dockerfile
|
16
|
+
# This is the name of the image where the built-image should be uploaded
|
17
|
+
# to when build. You should not include any tag after the name. The
|
18
|
+
# commit ref that you're building will be automatically added.
|
19
|
+
image-name: myorg/myapp
|
@@ -0,0 +1,29 @@
|
|
1
|
+
kind: Deployment
|
2
|
+
apiVersion: apps/v1
|
3
|
+
metadata:
|
4
|
+
name: web
|
5
|
+
spec:
|
6
|
+
selector:
|
7
|
+
matchLabels:
|
8
|
+
process_type: web
|
9
|
+
replicas: 1
|
10
|
+
template:
|
11
|
+
metadata:
|
12
|
+
labels:
|
13
|
+
process_type: web
|
14
|
+
spec:
|
15
|
+
containers:
|
16
|
+
- name: myapp
|
17
|
+
image: "{{ builds.app.image-name }}:{{ commit.ref }}"
|
18
|
+
imagePullPolicy: Always
|
19
|
+
command:
|
20
|
+
- bundle
|
21
|
+
- exec
|
22
|
+
- puma
|
23
|
+
- -C
|
24
|
+
- config/puma.rb
|
25
|
+
envFrom:
|
26
|
+
- configMapRef:
|
27
|
+
name: env-vars
|
28
|
+
ports:
|
29
|
+
- containerPort: 3000
|
@@ -0,0 +1,26 @@
|
|
1
|
+
kind: Deployment
|
2
|
+
apiVersion: apps/v1
|
3
|
+
metadata:
|
4
|
+
name: worker
|
5
|
+
spec:
|
6
|
+
selector:
|
7
|
+
matchLabels:
|
8
|
+
app: worker
|
9
|
+
replicas: 1
|
10
|
+
template:
|
11
|
+
metadata:
|
12
|
+
labels:
|
13
|
+
app: worker
|
14
|
+
spec:
|
15
|
+
containers:
|
16
|
+
- name: myapp
|
17
|
+
image: "{{ builds.app.image-name }}:{{ commit.ref }}"
|
18
|
+
imagePullPolicy: Always
|
19
|
+
command:
|
20
|
+
- bundle
|
21
|
+
- exec
|
22
|
+
- rake
|
23
|
+
- jobs:work
|
24
|
+
envFrom:
|
25
|
+
- configMapRef:
|
26
|
+
name: env-vars
|
@@ -0,0 +1,22 @@
|
|
1
|
+
kind: Job
|
2
|
+
apiVersion: batch/v1
|
3
|
+
metadata:
|
4
|
+
name: load-schema
|
5
|
+
spec:
|
6
|
+
backoffLimit: 0
|
7
|
+
ttlSecondsAfterFinished: 300
|
8
|
+
template:
|
9
|
+
spec:
|
10
|
+
restartPolicy: Never
|
11
|
+
containers:
|
12
|
+
- name: myapp
|
13
|
+
image: "{{ builds.app.image-name }}:{{ commit.ref }}"
|
14
|
+
imagePullPolicy: Always
|
15
|
+
command:
|
16
|
+
- bundle
|
17
|
+
- exec
|
18
|
+
- rake
|
19
|
+
- db:schema:load
|
20
|
+
envFrom:
|
21
|
+
- configMapRef:
|
22
|
+
name: env-vars
|
@@ -0,0 +1,22 @@
|
|
1
|
+
kind: Job
|
2
|
+
apiVersion: batch/v1
|
3
|
+
metadata:
|
4
|
+
name: migration
|
5
|
+
spec:
|
6
|
+
backoffLimit: 0
|
7
|
+
ttlSecondsAfterFinished: 60
|
8
|
+
template:
|
9
|
+
spec:
|
10
|
+
restartPolicy: Never
|
11
|
+
containers:
|
12
|
+
- name: myapp
|
13
|
+
image: "{{ builds.app.image-name }}:{{ commit.ref }}"
|
14
|
+
imagePullPolicy: Always
|
15
|
+
command:
|
16
|
+
- bundle
|
17
|
+
- exec
|
18
|
+
- rake
|
19
|
+
- db:migrate
|
20
|
+
envFrom:
|
21
|
+
- configMapRef:
|
22
|
+
name: env-vars
|
data.tar.gz.sig
ADDED
Binary file
|