hippo-cli 1.0.1 → 1.1.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
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/cli/apply_config.rb +4 -5
- data/cli/apply_services.rb +3 -3
- data/cli/console.rb +6 -6
- data/cli/deploy.rb +11 -22
- data/cli/install.rb +22 -23
- data/cli/kubectl.rb +3 -3
- data/cli/secrets_edit.rb +34 -0
- data/cli/secrets_key.rb +20 -0
- data/lib/hippo.rb +19 -0
- data/lib/hippo/cli.rb +210 -0
- data/lib/hippo/deployment_monitor.rb +82 -0
- data/lib/hippo/error.rb +0 -12
- data/lib/hippo/image.rb +74 -0
- data/lib/hippo/manifest.rb +81 -0
- data/lib/hippo/object_definition.rb +63 -0
- data/lib/hippo/secret.rb +61 -114
- data/lib/hippo/secret_manager.rb +43 -32
- data/lib/hippo/stage.rb +157 -7
- data/lib/hippo/util.rb +56 -33
- data/lib/hippo/version.rb +1 -1
- data/template/Hippofile +12 -13
- data/template/config/{production/env-vars.yaml → env-vars.yaml} +0 -0
- data/template/jobs/{upgrade → deploy}/db-migration.yaml +0 -0
- data/template/stages/production.yaml +1 -0
- metadata +31 -16
- metadata.gz.sig +0 -0
- data/cli/build.rb +0 -16
- data/cli/edit-secret.rb +0 -45
- data/cli/objects.rb +0 -34
- data/cli/publish.rb +0 -17
- data/cli/secrets.rb +0 -23
- data/cli/status.rb +0 -15
- data/lib/hippo/build_spec.rb +0 -32
- data/lib/hippo/cli_steps.rb +0 -315
- data/lib/hippo/kubernetes.rb +0 -200
- data/lib/hippo/recipe.rb +0 -126
- data/lib/hippo/repository.rb +0 -122
- data/lib/hippo/yaml_part.rb +0 -47
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 862958b0818bb34f9956e83e82738d1b59b2d4fba32c8bde858b9793129b0c9c
|
4
|
+
data.tar.gz: 9f3fd4e9587b882cb6588058b94efa85c8049bb31869b013c3a13f81edca5525
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f04805d52992d14804d21671dfbc59e1ea85dc28a3d490f135353f3cf6ba94830c1f01a0f5b1f16e3e8cabf2cd433a03e883ded0f7227f727abaa6ecf9d35f83
|
7
|
+
data.tar.gz: 6aed2eaa5a88fd0f9b99eaeca58de8f208eb72825bee8a7c4ef78068092ecf3e3a901ae0b9784e05a78d7785091d22ac1e4ac523897ca1f696eaeaf39da6eedb
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data.tar.gz.sig
CHANGED
Binary file
|
data/cli/apply_config.rb
CHANGED
@@ -8,10 +8,9 @@ command :'apply-config' do
|
|
8
8
|
end
|
9
9
|
|
10
10
|
action do |context|
|
11
|
-
require 'hippo/
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
steps.apply_secrets
|
11
|
+
require 'hippo/cli'
|
12
|
+
cli = Hippo::CLI.setup(context)
|
13
|
+
cli.apply_namespace
|
14
|
+
cli.apply_config
|
16
15
|
end
|
17
16
|
end
|
data/cli/apply_services.rb
CHANGED
data/cli/console.rb
CHANGED
@@ -16,16 +16,16 @@ command :console do
|
|
16
16
|
end
|
17
17
|
|
18
18
|
action do |context|
|
19
|
-
require 'hippo/
|
20
|
-
cli = Hippo::
|
19
|
+
require 'hippo/cli'
|
20
|
+
cli = Hippo::CLI.setup(context)
|
21
21
|
|
22
|
-
if cli.
|
22
|
+
if cli.manifest.console.nil?
|
23
23
|
raise Error, 'No console configuration has been provided in Hippofile'
|
24
24
|
end
|
25
25
|
|
26
26
|
time = Time.now.to_i
|
27
|
-
deployment_name = context.options[:deployment] || cli.
|
28
|
-
command = context.options[:command] || cli.
|
29
|
-
exec cli.stage.kubectl("exec -it deployment/#{deployment_name} -- #{command}")
|
27
|
+
deployment_name = context.options[:deployment] || cli.manifest.console['deployment']
|
28
|
+
command = context.options[:command] || cli.manifest.console['command'] || 'bash'
|
29
|
+
exec cli.stage.kubectl("exec -it deployment/#{deployment_name} -- #{command}").join(' ')
|
30
30
|
end
|
31
31
|
end
|
data/cli/deploy.rb
CHANGED
@@ -11,35 +11,24 @@ command :deploy do
|
|
11
11
|
options[:jobs] = false
|
12
12
|
end
|
13
13
|
|
14
|
-
option '--no-build', 'Do not build the images' do |_value, options|
|
15
|
-
options[:build] = false
|
16
|
-
end
|
17
|
-
|
18
14
|
action do |context|
|
19
|
-
require 'hippo/
|
20
|
-
|
21
|
-
|
22
|
-
commit = steps.recipe.repository.commit_for_branch(steps.stage.branch)
|
23
|
-
puts 'Not building an image and just hoping one exists for current commit.'
|
24
|
-
puts "Using #{commit.objectish} from #{steps.stage.branch}"
|
25
|
-
steps.prepare_repository(fetch: false)
|
26
|
-
else
|
27
|
-
steps.prepare_repository
|
28
|
-
steps.build
|
29
|
-
steps.publish
|
30
|
-
end
|
15
|
+
require 'hippo/cli'
|
16
|
+
cli = Hippo::CLI.setup(context)
|
17
|
+
cli.verify_image_existence
|
31
18
|
|
32
|
-
|
33
|
-
|
34
|
-
steps.apply_secrets
|
19
|
+
cli.apply_namespace
|
20
|
+
cli.apply_config
|
35
21
|
|
36
22
|
unless context.options[:jobs] == false
|
37
|
-
if
|
23
|
+
if cli.run_deploy_jobs == false
|
38
24
|
raise Hippo::Error, 'Not all jobs completed successfully. Cannot continue with deployment.'
|
39
25
|
end
|
40
26
|
end
|
41
27
|
|
42
|
-
|
43
|
-
|
28
|
+
unless cli.deploy
|
29
|
+
puts 'Deployment did not complete successfully. Not continuing any further.'
|
30
|
+
exit 2
|
31
|
+
end
|
32
|
+
cli.apply_services
|
44
33
|
end
|
45
34
|
end
|
data/cli/install.rb
CHANGED
@@ -7,38 +7,37 @@ command :install do
|
|
7
7
|
options[:hippofile] = value.to_s
|
8
8
|
end
|
9
9
|
|
10
|
-
option '--no-build', 'Do not build the images' do |_value, options|
|
11
|
-
options[:build] = false
|
12
|
-
end
|
13
|
-
|
14
10
|
option '--no-deploy', 'Do not deploy after install' do |_value, options|
|
15
11
|
options[:deploy] = false
|
16
12
|
end
|
17
13
|
|
14
|
+
option '--no-jobs', 'Do not run the deploy jobs' do |_value, options|
|
15
|
+
options[:jobs] = false
|
16
|
+
end
|
17
|
+
|
18
18
|
action do |context|
|
19
|
-
require 'hippo/
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
else
|
26
|
-
steps.prepare_repository
|
27
|
-
steps.build
|
28
|
-
steps.publish
|
29
|
-
end
|
19
|
+
require 'hippo/cli'
|
20
|
+
cli = Hippo::CLI.setup(context)
|
21
|
+
cli.verify_image_existence
|
22
|
+
|
23
|
+
cli.apply_namespace
|
24
|
+
cli.apply_config
|
30
25
|
|
31
|
-
|
32
|
-
|
33
|
-
|
26
|
+
unless context.options[:jobs] == false
|
27
|
+
if cli.run_install_jobs == false
|
28
|
+
raise Hippo::Error, 'Not all jobs completed successfully. Cannot continue with installation.'
|
29
|
+
end
|
30
|
+
end
|
34
31
|
|
35
|
-
if
|
36
|
-
|
32
|
+
if options[:deploy] == false
|
33
|
+
puts 'Not deploying because --no-deploy was specified'
|
34
|
+
exit 0
|
37
35
|
end
|
38
36
|
|
39
|
-
unless
|
40
|
-
|
41
|
-
|
37
|
+
unless cli.deploy
|
38
|
+
puts 'Deployment did not complete successfully. Not continuing any further.'
|
39
|
+
exit 2
|
42
40
|
end
|
41
|
+
cli.apply_services
|
43
42
|
end
|
44
43
|
end
|
data/cli/kubectl.rb
CHANGED
@@ -8,10 +8,10 @@ command :kubectl do
|
|
8
8
|
end
|
9
9
|
|
10
10
|
action do |context|
|
11
|
-
require 'hippo/
|
12
|
-
cli = Hippo::
|
11
|
+
require 'hippo/cli'
|
12
|
+
cli = Hippo::CLI.setup(context)
|
13
13
|
ARGV.shift(2)
|
14
14
|
ARGV.delete('--')
|
15
|
-
exec cli.stage.kubectl(*ARGV)
|
15
|
+
exec cli.stage.kubectl(*ARGV).join(' ')
|
16
16
|
end
|
17
17
|
end
|
data/cli/secrets_edit.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
command :'secrets:edit' do
|
4
|
+
desc 'Create/edit an encrypted secrets file'
|
5
|
+
|
6
|
+
action do |context|
|
7
|
+
require 'hippo/cli'
|
8
|
+
cli = Hippo::CLI.setup(context)
|
9
|
+
|
10
|
+
secret_name = context.args[0]
|
11
|
+
raise Hippo::Error, 'You must provide a secret name' if secret_name.nil?
|
12
|
+
|
13
|
+
manager = cli.stage.secret_manager
|
14
|
+
unless manager.key_available?
|
15
|
+
puts "\e[31mNo key has been published for this stage yet.\e[0m"
|
16
|
+
puts "Use `hippo #{cli.stage.name} secrets:key --generate` to generate one."
|
17
|
+
exit 2
|
18
|
+
end
|
19
|
+
|
20
|
+
secret = manager.secret(secret_name)
|
21
|
+
if secret.exists?
|
22
|
+
secret.edit
|
23
|
+
else
|
24
|
+
puts "No secret exists at #{secret.path}. Would you like to create one?"
|
25
|
+
response = STDIN.gets.strip.downcase.strip
|
26
|
+
if %w[y yes please].include?(response)
|
27
|
+
secret.create
|
28
|
+
secret.edit
|
29
|
+
else
|
30
|
+
puts 'Not a problem. You can make it later.'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/cli/secrets_key.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
command :'secrets:key' do
|
4
|
+
desc 'Display/generate details about the secret encryption key'
|
5
|
+
|
6
|
+
action do |context|
|
7
|
+
require 'hippo/cli'
|
8
|
+
cli = Hippo::CLI.setup(context)
|
9
|
+
sm = cli.stage.secret_manager
|
10
|
+
if sm.key_available?
|
11
|
+
puts 'Secret encryption key is stored in secret/hippo-secret-key.'
|
12
|
+
else
|
13
|
+
puts 'Secret encryption key has not been generated yet.'
|
14
|
+
puts 'Generate a new using:'
|
15
|
+
puts
|
16
|
+
puts " hippo #{cli.stage.name} secrets:key --generate"
|
17
|
+
puts
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/hippo.rb
CHANGED
@@ -1,7 +1,26 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Hippo
|
4
|
+
# The path where the user configuration file is stored
|
5
|
+
CONFIG_PATH = File.join(ENV['HOME'], '.hippo', 'config.yaml')
|
6
|
+
|
7
|
+
# Return the root to the gem
|
8
|
+
#
|
9
|
+
# @return [String]
|
4
10
|
def self.root
|
5
11
|
File.expand_path('../', __dir__)
|
6
12
|
end
|
13
|
+
|
14
|
+
# User the user configuration for Hippo
|
15
|
+
#
|
16
|
+
# @return [Hash]
|
17
|
+
def self.config
|
18
|
+
@config ||= begin
|
19
|
+
if File.file?(CONFIG_PATH)
|
20
|
+
YAML.load_file(CONFIG_PATH)
|
21
|
+
else
|
22
|
+
{}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
7
26
|
end
|
data/lib/hippo/cli.rb
ADDED
@@ -0,0 +1,210 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'securerandom'
|
4
|
+
require 'hippo/manifest'
|
5
|
+
require 'hippo/deployment_monitor'
|
6
|
+
|
7
|
+
module Hippo
|
8
|
+
class CLI
|
9
|
+
attr_reader :manifest
|
10
|
+
attr_reader :stage
|
11
|
+
|
12
|
+
# Initialize a new CLI instance
|
13
|
+
#
|
14
|
+
# @param manifest [Hippo::Manifest]
|
15
|
+
# @param stage [Hippo::Stage]
|
16
|
+
# @return [Hippo::CLI]
|
17
|
+
def initialize(manifest, stage)
|
18
|
+
@manifest = manifest
|
19
|
+
@stage = stage
|
20
|
+
end
|
21
|
+
|
22
|
+
# Verify image existence
|
23
|
+
#
|
24
|
+
# @return [void]
|
25
|
+
def verify_image_existence
|
26
|
+
missing = 0
|
27
|
+
@manifest.images.each do |_, image|
|
28
|
+
commit = image.commit_ref_for_branch(@stage.branch)
|
29
|
+
if image.exists_for_commit?(commit)
|
30
|
+
puts "Image for #{image.name} exists for #{image.url} (with tag #{commit})"
|
31
|
+
else
|
32
|
+
missing += 1
|
33
|
+
puts "No #{image.name} image at #{image.url} (with tag #{commit})"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
if missing > 0
|
38
|
+
raise Error, "#{missing} #{missing == 1 ? 'image was' : 'images were'} not available. Cannot continue."
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Apply the namespace configuration
|
43
|
+
#
|
44
|
+
# @return [void]
|
45
|
+
def apply_namespace
|
46
|
+
od = Hippo::ObjectDefinition.new(
|
47
|
+
{
|
48
|
+
'kind' => 'Namespace',
|
49
|
+
'apiVersion' => 'v1',
|
50
|
+
'metadata' => { 'name' => @stage.namespace, 'labels' => { 'name' => @stage.namespace } }
|
51
|
+
},
|
52
|
+
@stage
|
53
|
+
)
|
54
|
+
apply([od], 'namespace')
|
55
|
+
end
|
56
|
+
|
57
|
+
# Apply all configuration and secrets
|
58
|
+
#
|
59
|
+
# @return [void]
|
60
|
+
def apply_config
|
61
|
+
apply(@stage.configs, 'configuration')
|
62
|
+
|
63
|
+
if @stage.secret_manager.key_available?
|
64
|
+
secrets = @stage.secret_manager.secrets.map(&:applyable_yaml).flatten
|
65
|
+
apply(secrets, 'secret')
|
66
|
+
else
|
67
|
+
puts 'Not applying secrets because no key is available to decrypt them.'
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Apply all services, ingresses and policies
|
72
|
+
#
|
73
|
+
# @return [void]
|
74
|
+
def apply_services
|
75
|
+
apply(@stage.services, 'service')
|
76
|
+
end
|
77
|
+
|
78
|
+
# Run all deploy jobs
|
79
|
+
#
|
80
|
+
# @return [void]
|
81
|
+
def run_deploy_jobs
|
82
|
+
run_jobs('deploy')
|
83
|
+
end
|
84
|
+
|
85
|
+
# Run all install jobs
|
86
|
+
#
|
87
|
+
# @return [void]
|
88
|
+
def run_install_jobs
|
89
|
+
run_jobs('install')
|
90
|
+
end
|
91
|
+
|
92
|
+
# Run a full deployment
|
93
|
+
#
|
94
|
+
# @return [void]
|
95
|
+
def deploy
|
96
|
+
deployment_id = SecureRandom.hex(6)
|
97
|
+
deployments = @stage.deployments
|
98
|
+
if deployments.empty?
|
99
|
+
puts 'There are no deployment objects defined.'
|
100
|
+
return true
|
101
|
+
end
|
102
|
+
|
103
|
+
puts "Using deployment ID: #{deployment_id}"
|
104
|
+
|
105
|
+
deployments.each do |deployment|
|
106
|
+
deployment.insert_deployment_id!(deployment_id)
|
107
|
+
end
|
108
|
+
|
109
|
+
apply(deployments, 'deployment')
|
110
|
+
puts 'Waiting for all deployments to roll out...'
|
111
|
+
|
112
|
+
monitor = DeploymentMonitor.new(@stage, deployment_id)
|
113
|
+
monitor.on_success do |poll|
|
114
|
+
if poll.replica_sets.size == 1
|
115
|
+
puts "\e[32mDeployment rolled out successfully\e[0m"
|
116
|
+
else
|
117
|
+
puts "\e[32mAll #{poll.replica_sets.size} deployments all rolled out successfully\e[0m"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
monitor.on_wait do |poll|
|
122
|
+
puts "Waiting for #{poll.pending.size} #{poll.pending.size == 1 ? 'deployment' : 'deployments'} (#{poll.pending_names.join(', ')})"
|
123
|
+
end
|
124
|
+
|
125
|
+
monitor.on_failure do |poll|
|
126
|
+
puts "\e[31mLooks like things aren't going to plan with some deployments.\e[0m"
|
127
|
+
puts 'You can review potential issues using the commads below:'
|
128
|
+
|
129
|
+
poll.pending.each do |rs|
|
130
|
+
puts
|
131
|
+
name = rs.name.split('-').first
|
132
|
+
puts " hippo #{@stage.name} kubectl -- describe deployment \e[35m#{name}\e[0m"
|
133
|
+
puts " hippo #{@stage.name} kubectl -- logs deployment/\e[35m#{name}\e[0m --all-containers"
|
134
|
+
end
|
135
|
+
puts
|
136
|
+
end
|
137
|
+
|
138
|
+
monitor.wait
|
139
|
+
end
|
140
|
+
|
141
|
+
private
|
142
|
+
|
143
|
+
def apply(objects, type)
|
144
|
+
puts "Applying #{objects.size} #{type} #{objects.size == 1 ? 'object' : 'objects'}"
|
145
|
+
@stage.apply(objects)
|
146
|
+
end
|
147
|
+
|
148
|
+
def run_jobs(type)
|
149
|
+
puts "Running #{type} jobs"
|
150
|
+
jobs = @stage.jobs(type)
|
151
|
+
if jobs.empty?
|
152
|
+
puts "There are no #{type} jobs to run"
|
153
|
+
return true
|
154
|
+
end
|
155
|
+
|
156
|
+
jobs.each do |job|
|
157
|
+
@stage.delete('job', job.name)
|
158
|
+
end
|
159
|
+
|
160
|
+
applied_jobs = apply(jobs, 'deploy job')
|
161
|
+
|
162
|
+
timeout, jobs = @stage.wait_for_jobs(applied_jobs.keys)
|
163
|
+
success_jobs = []
|
164
|
+
failed_jobs = []
|
165
|
+
jobs.each do |job|
|
166
|
+
if job['status']['succeeded']
|
167
|
+
success_jobs << job
|
168
|
+
else
|
169
|
+
failed_jobs << job
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
if success_jobs.size == jobs.size
|
174
|
+
puts 'All jobs completed successfully'
|
175
|
+
puts 'You can review the logs for these by running the commands below'
|
176
|
+
puts
|
177
|
+
result = true
|
178
|
+
else
|
179
|
+
puts "\e[31mNot all install jobs completed successfully.\e[0m"
|
180
|
+
puts 'You should review the logs for these using the commands below'
|
181
|
+
puts
|
182
|
+
result = false
|
183
|
+
end
|
184
|
+
|
185
|
+
jobs.each do |job|
|
186
|
+
icon = if job['status']['succeeded']
|
187
|
+
'✅'
|
188
|
+
else
|
189
|
+
'❌'
|
190
|
+
end
|
191
|
+
puts " #{icon} " + @stage.kubectl("logs job/#{job.name}").join(' ')
|
192
|
+
end
|
193
|
+
puts
|
194
|
+
result
|
195
|
+
end
|
196
|
+
|
197
|
+
class << self
|
198
|
+
def setup(context)
|
199
|
+
manifest = Hippo::Manifest.load_from_file(context.options[:hippofile] || './Hippofile')
|
200
|
+
|
201
|
+
stage = manifest.stages[CURRENT_STAGE]
|
202
|
+
if stage.nil?
|
203
|
+
raise Error, "Invalid stage name `#{CURRENT_STAGE}`. Check this has been defined in in your stages directory with a matching name?"
|
204
|
+
end
|
205
|
+
|
206
|
+
new(manifest, stage)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|