hippo-cli 1.1.1 → 1.2.3
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/bin/hippo +1 -1
- data/cli/apply_config.rb +0 -4
- data/cli/apply_services.rb +0 -4
- data/cli/create.rb +9 -13
- data/cli/deploy.rb +0 -4
- data/cli/exec.rb +23 -0
- data/cli/install.rb +0 -4
- data/cli/key.rb +1 -4
- data/cli/kubectl.rb +0 -4
- data/cli/logs.rb +0 -4
- data/cli/objects.rb +0 -4
- data/cli/package_install.rb +0 -4
- data/cli/package_list.rb +0 -4
- data/cli/package_notes.rb +0 -4
- data/cli/package_test.rb +0 -4
- data/cli/package_uninstall.rb +0 -4
- data/cli/package_upgrade.rb +0 -4
- data/cli/package_values.rb +1 -5
- data/cli/prepare.rb +0 -4
- data/cli/readme.rb +18 -0
- data/cli/run.rb +1 -5
- data/cli/secrets.rb +0 -4
- data/cli/setup.rb +71 -0
- data/cli/stages.rb +4 -7
- data/cli/status.rb +0 -4
- data/cli/tidy.rb +33 -0
- data/cli/update.rb +22 -0
- data/cli/vars.rb +0 -4
- data/lib/hippo.rb +7 -0
- data/lib/hippo/bootstrap_parser.rb +22 -0
- data/lib/hippo/cli.rb +31 -27
- data/lib/hippo/image.rb +7 -1
- data/lib/hippo/liquid_filters.rb +15 -0
- data/lib/hippo/manifest.rb +17 -17
- data/lib/hippo/object_definition.rb +1 -1
- data/lib/hippo/package.rb +1 -1
- data/lib/hippo/repository_tag.rb +3 -2
- data/lib/hippo/secret_manager.rb +27 -22
- data/lib/hippo/stage.rb +86 -40
- data/lib/hippo/util.rb +61 -1
- data/lib/hippo/version.rb +1 -1
- data/lib/hippo/working_directory.rb +180 -0
- data/template/Hippofile +1 -2
- metadata +10 -26
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/cli/console.rb +0 -33
- metadata.gz.sig +0 -0
data/cli/status.rb
CHANGED
@@ -3,10 +3,6 @@
|
|
3
3
|
command :status do
|
4
4
|
desc 'Show current status of the namespace'
|
5
5
|
|
6
|
-
option '-h', '--hippofile [RECIPE]', 'The path to the Hippofile (defaults: ./Hippofile)' do |value, options|
|
7
|
-
options[:hippofile] = value.to_s
|
8
|
-
end
|
9
|
-
|
10
6
|
option '--full', 'Include all relevant objects in namespace' do |_value, options|
|
11
7
|
options[:full] = true
|
12
8
|
end
|
data/cli/tidy.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
command :tidy do
|
4
|
+
desc 'Remove live objects that are not referenced by the manifest'
|
5
|
+
action do |context|
|
6
|
+
require 'hippo/cli'
|
7
|
+
cli = Hippo::CLI.setup(context)
|
8
|
+
cli.preflight
|
9
|
+
|
10
|
+
objects_to_prune = cli.stage.live_objects(pruneable_only: true)
|
11
|
+
if objects_to_prune.empty?
|
12
|
+
puts 'There are no objects to tidy'
|
13
|
+
exit 0
|
14
|
+
end
|
15
|
+
|
16
|
+
puts "Found #{objects_to_prune.size} object(s) to tidy"
|
17
|
+
puts
|
18
|
+
objects_to_prune.each do |obj|
|
19
|
+
$stdout.print ' ' + obj[:live].kind.ljust(25)
|
20
|
+
$stdout.print obj[:live].name
|
21
|
+
puts
|
22
|
+
end
|
23
|
+
puts
|
24
|
+
|
25
|
+
require 'hippo/util'
|
26
|
+
unless Hippo::Util.confirm('Do you wish to continue?')
|
27
|
+
puts 'No problem, not removing anything right now'
|
28
|
+
exit 0
|
29
|
+
end
|
30
|
+
|
31
|
+
cli.stage.delete_pruneable_objects
|
32
|
+
end
|
33
|
+
end
|
data/cli/update.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
command :update do
|
4
|
+
desc 'Get the latest updates from the remote manifest'
|
5
|
+
action do
|
6
|
+
require 'hippo/working_directory'
|
7
|
+
wd = Hippo::WorkingDirectory.new
|
8
|
+
|
9
|
+
unless wd.can_update?
|
10
|
+
puts "No need to update #{wd.source_type} manifests"
|
11
|
+
exit 0
|
12
|
+
end
|
13
|
+
|
14
|
+
if wd.last_updated_at
|
15
|
+
puts "Last updated: #{wd.last_updated_at}"
|
16
|
+
else
|
17
|
+
puts 'Does not exist yet. Downloading for the first time.'
|
18
|
+
end
|
19
|
+
|
20
|
+
wd.update_from_remote(verbose: true)
|
21
|
+
end
|
22
|
+
end
|
data/cli/vars.rb
CHANGED
@@ -3,10 +3,6 @@
|
|
3
3
|
command :vars do
|
4
4
|
desc 'Show all variables available for use in this stage'
|
5
5
|
|
6
|
-
option '-h', '--hippofile [RECIPE]', 'The path to the Hippofile (defaults: ./Hippofile)' do |value, options|
|
7
|
-
options[:hippofile] = value.to_s
|
8
|
-
end
|
9
|
-
|
10
6
|
action do |context|
|
11
7
|
require 'hippo/cli'
|
12
8
|
cli = Hippo::CLI.setup(context)
|
data/lib/hippo.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'securerandom'
|
4
4
|
require 'secure_random_string'
|
5
|
+
require 'openssl'
|
5
6
|
|
6
7
|
module Hippo
|
7
8
|
class BootstrapParser
|
@@ -54,6 +55,27 @@ module Hippo
|
|
54
55
|
SecureRandom.hex(value['size'] ? value['size'].to_i : 16)
|
55
56
|
when 'random'
|
56
57
|
Base64.encode64(SecureRandom.random_bytes(value['size'] ? value['size'].to_i : 16)).strip
|
58
|
+
when 'rsa'
|
59
|
+
OpenSSL::PKey::RSA.new(value['size'] ? value['size'].to_i : 2048).to_s
|
60
|
+
when 'certificate'
|
61
|
+
key = OpenSSL::PKey::RSA.new(value['key_size'] ? value['key_size'].to_i : 2048)
|
62
|
+
|
63
|
+
cert = OpenSSL::X509::Certificate.new
|
64
|
+
cert.subject = cert.issuer = OpenSSL::X509::Name.new(
|
65
|
+
[
|
66
|
+
['C', value['country'] || 'GB'],
|
67
|
+
['O', value['organization'] || 'Default'],
|
68
|
+
['OU', value['organization_unit'] || 'Default'],
|
69
|
+
['CN', value['common_name'] || 'default']
|
70
|
+
]
|
71
|
+
)
|
72
|
+
cert.not_before = Time.now
|
73
|
+
cert.not_after = Time.now + (60 * 60 * 24 * (value['days'] ? value['days'].to_i : 730))
|
74
|
+
cert.public_key = key.public_key
|
75
|
+
cert.serial = 0x0
|
76
|
+
cert.version = 2
|
77
|
+
cert.sign key, OpenSSL::Digest::SHA256.new
|
78
|
+
{ 'certificate' => cert.to_s, 'key' => key.to_s }
|
57
79
|
when nil
|
58
80
|
raise Error, "A 'type' must be provided for each generated item"
|
59
81
|
else
|
data/lib/hippo/cli.rb
CHANGED
@@ -3,28 +3,32 @@
|
|
3
3
|
require 'securerandom'
|
4
4
|
require 'hippo/manifest'
|
5
5
|
require 'hippo/deployment_monitor'
|
6
|
+
require 'hippo/working_directory'
|
6
7
|
|
7
8
|
module Hippo
|
8
9
|
class CLI
|
9
|
-
attr_reader :
|
10
|
+
attr_reader :wd
|
10
11
|
attr_reader :stage
|
11
12
|
|
12
13
|
# Initialize a new CLI instance
|
13
14
|
#
|
14
|
-
# @param
|
15
|
-
# @param stage [Hippo::Stage]
|
15
|
+
# @param wd [Hippo::WorkingDirectory]
|
16
16
|
# @return [Hippo::CLI]
|
17
|
-
def initialize(
|
18
|
-
@
|
17
|
+
def initialize(wd, stage)
|
18
|
+
@wd = wd
|
19
19
|
@stage = stage
|
20
20
|
end
|
21
21
|
|
22
|
+
def manifest
|
23
|
+
wd.manifest
|
24
|
+
end
|
25
|
+
|
22
26
|
# Verify image existence
|
23
27
|
#
|
24
28
|
# @return [void]
|
25
29
|
def verify_image_existence
|
26
30
|
missing = 0
|
27
|
-
|
31
|
+
stage.images.each do |_, image|
|
28
32
|
if image.exists?
|
29
33
|
puts "Image for #{image.name} exists at #{image.image_url}"
|
30
34
|
else
|
@@ -43,7 +47,7 @@ module Hippo
|
|
43
47
|
#
|
44
48
|
# @return [void]
|
45
49
|
def preflight
|
46
|
-
if
|
50
|
+
if stage.context.nil?
|
47
51
|
puts "\e[33mStage does not specify a context. The current context specified"
|
48
52
|
puts "by the kubectl config will be used (#{Hippo.current_kubectl_context}).\e[0m"
|
49
53
|
puts
|
@@ -61,9 +65,9 @@ module Hippo
|
|
61
65
|
{
|
62
66
|
'kind' => 'Namespace',
|
63
67
|
'apiVersion' => 'v1',
|
64
|
-
'metadata' => { 'name' =>
|
68
|
+
'metadata' => { 'name' => stage.namespace, 'labels' => { 'name' => stage.namespace } }
|
65
69
|
},
|
66
|
-
|
70
|
+
stage
|
67
71
|
)
|
68
72
|
apply([od], 'namespace')
|
69
73
|
end
|
@@ -72,19 +76,19 @@ module Hippo
|
|
72
76
|
#
|
73
77
|
# @return [void]
|
74
78
|
def apply_config
|
75
|
-
apply(
|
79
|
+
apply(stage.configs, 'configuration')
|
76
80
|
end
|
77
81
|
|
78
82
|
# Install all packages
|
79
83
|
#
|
80
84
|
# @return [void]
|
81
85
|
def install_all_packages
|
82
|
-
if
|
86
|
+
if stage.packages.empty?
|
83
87
|
puts 'There are no packages to install'
|
84
88
|
return
|
85
89
|
end
|
86
90
|
|
87
|
-
|
91
|
+
stage.packages.values.each do |package|
|
88
92
|
if package.installed?
|
89
93
|
puts "#{package.name} is already installed. Upgrading..."
|
90
94
|
package.upgrade
|
@@ -94,14 +98,14 @@ module Hippo
|
|
94
98
|
end
|
95
99
|
end
|
96
100
|
|
97
|
-
puts "Finished with #{
|
101
|
+
puts "Finished with #{stage.packages.size} #{stage.packages.size == 1 ? 'package' : 'packages'}"
|
98
102
|
end
|
99
103
|
|
100
104
|
# Apply all services, ingresses and policies
|
101
105
|
#
|
102
106
|
# @return [void]
|
103
107
|
def apply_services
|
104
|
-
apply(
|
108
|
+
apply(stage.services, 'service')
|
105
109
|
end
|
106
110
|
|
107
111
|
# Run all deploy jobs
|
@@ -123,7 +127,7 @@ module Hippo
|
|
123
127
|
# @return [void]
|
124
128
|
def deploy
|
125
129
|
deployment_id = SecureRandom.hex(6)
|
126
|
-
deployments =
|
130
|
+
deployments = stage.deployments
|
127
131
|
if deployments.empty?
|
128
132
|
puts 'There are no deployment objects defined'
|
129
133
|
return true
|
@@ -138,7 +142,7 @@ module Hippo
|
|
138
142
|
apply(deployments, 'deployment')
|
139
143
|
puts 'Waiting for all deployments to roll out...'
|
140
144
|
|
141
|
-
monitor = DeploymentMonitor.new(
|
145
|
+
monitor = DeploymentMonitor.new(stage, deployment_id)
|
142
146
|
monitor.on_success do |poll|
|
143
147
|
if poll.replica_sets.size == 1
|
144
148
|
puts "\e[32mDeployment rolled out successfully\e[0m"
|
@@ -158,8 +162,8 @@ module Hippo
|
|
158
162
|
poll.pending.each do |rs|
|
159
163
|
puts
|
160
164
|
name = rs.name.split('-').first
|
161
|
-
puts " hippo #{
|
162
|
-
puts " hippo #{
|
165
|
+
puts " hippo #{stage.name} kubectl -- describe deployment \e[35m#{name}\e[0m"
|
166
|
+
puts " hippo #{stage.name} kubectl -- logs deployment/\e[35m#{name}\e[0m --all-containers"
|
163
167
|
end
|
164
168
|
puts
|
165
169
|
end
|
@@ -174,25 +178,25 @@ module Hippo
|
|
174
178
|
puts "No #{type} objects found to apply"
|
175
179
|
else
|
176
180
|
puts "Applying #{objects.size} #{type} #{objects.size == 1 ? 'object' : 'objects'}"
|
177
|
-
|
181
|
+
stage.apply(objects)
|
178
182
|
end
|
179
183
|
end
|
180
184
|
|
181
185
|
def run_jobs(type)
|
182
186
|
puts "Running #{type} jobs"
|
183
|
-
jobs =
|
187
|
+
jobs = stage.jobs(type)
|
184
188
|
if jobs.empty?
|
185
189
|
puts "There are no #{type} jobs to run"
|
186
190
|
return true
|
187
191
|
end
|
188
192
|
|
189
193
|
jobs.each do |job|
|
190
|
-
|
194
|
+
stage.delete('job', job.name)
|
191
195
|
end
|
192
196
|
|
193
197
|
applied_jobs = apply(jobs, 'deploy job')
|
194
198
|
|
195
|
-
timeout, jobs =
|
199
|
+
timeout, jobs = stage.wait_for_jobs(applied_jobs.keys)
|
196
200
|
success_jobs = []
|
197
201
|
failed_jobs = []
|
198
202
|
jobs.each do |job|
|
@@ -221,22 +225,22 @@ module Hippo
|
|
221
225
|
else
|
222
226
|
'❌'
|
223
227
|
end
|
224
|
-
puts " #{icon} hippo #{
|
228
|
+
puts " #{icon} hippo #{stage.name} kubectl -- logs job/#{job.name}"
|
225
229
|
end
|
226
230
|
puts
|
227
231
|
result
|
228
232
|
end
|
229
233
|
|
230
234
|
class << self
|
231
|
-
def setup(
|
232
|
-
|
235
|
+
def setup(_context)
|
236
|
+
wd = Hippo::WorkingDirectory.new
|
233
237
|
|
234
|
-
stage =
|
238
|
+
stage = wd.stages[CURRENT_STAGE]
|
235
239
|
if stage.nil?
|
236
240
|
raise Error, "Invalid stage name `#{CURRENT_STAGE}`. Check this has been defined in in your stages directory with a matching name?"
|
237
241
|
end
|
238
242
|
|
239
|
-
new(
|
243
|
+
new(wd, stage)
|
240
244
|
end
|
241
245
|
end
|
242
246
|
end
|
data/lib/hippo/image.rb
CHANGED
@@ -34,7 +34,11 @@ module Hippo
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def image_url
|
37
|
-
|
37
|
+
if host
|
38
|
+
"#{host}/#{image_name}:#{tag}"
|
39
|
+
else
|
40
|
+
"#{image_name}:#{tag}"
|
41
|
+
end
|
38
42
|
end
|
39
43
|
|
40
44
|
def template_vars
|
@@ -52,6 +56,8 @@ module Hippo
|
|
52
56
|
end
|
53
57
|
|
54
58
|
def exists?
|
59
|
+
return true unless tag.is_a?(RepositoryTag)
|
60
|
+
return true if host.nil?
|
55
61
|
return true unless can_check_for_existence?
|
56
62
|
|
57
63
|
credentials = Hippo.config.dig('docker', 'credentials', host)
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hippo
|
4
|
+
module LiquidFilters
|
5
|
+
def indent(text, depth = 2)
|
6
|
+
text.split("\n").map.each_with_index do |p, i|
|
7
|
+
i == 0 ? p : ' ' * depth + p
|
8
|
+
end.join("\n")
|
9
|
+
end
|
10
|
+
|
11
|
+
def multiline(text)
|
12
|
+
text.inspect
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/hippo/manifest.rb
CHANGED
@@ -36,6 +36,10 @@ module Hippo
|
|
36
36
|
@options['console']
|
37
37
|
end
|
38
38
|
|
39
|
+
def commands
|
40
|
+
@options['commands'] || {}
|
41
|
+
end
|
42
|
+
|
39
43
|
def config
|
40
44
|
@options['config'] || {}
|
41
45
|
end
|
@@ -51,6 +55,18 @@ module Hippo
|
|
51
55
|
end
|
52
56
|
end
|
53
57
|
|
58
|
+
def readme
|
59
|
+
return @readme if instance_variable_defined?('@readme')
|
60
|
+
|
61
|
+
path = File.join(@root, 'readme.txt')
|
62
|
+
unless File.file?(path)
|
63
|
+
@readme = nil
|
64
|
+
return
|
65
|
+
end
|
66
|
+
|
67
|
+
@readme = File.read(path)
|
68
|
+
end
|
69
|
+
|
54
70
|
def template_vars
|
55
71
|
{
|
56
72
|
'name' => name
|
@@ -63,29 +79,13 @@ module Hippo
|
|
63
79
|
@options['images']
|
64
80
|
end
|
65
81
|
|
66
|
-
# Load all stages that are available in the manifest
|
67
|
-
#
|
68
|
-
# @return [Hash<Symbol, Hippo::Stage>]
|
69
|
-
def stages
|
70
|
-
objects('stages').each_with_object({}) do |(_, objects), hash|
|
71
|
-
objects.each do |obj|
|
72
|
-
stage = Stage.new(self, obj)
|
73
|
-
hash[stage.name] = stage
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
82
|
# Load all YAML objects at a given path and return them.
|
79
83
|
#
|
80
84
|
# @param path [String]
|
81
85
|
# @param decorator [Proc] an optional parser to run across the raw YAML file
|
82
86
|
# @return [Array<Hash>]
|
83
87
|
def objects(path, decorator: nil)
|
84
|
-
|
85
|
-
files.each_with_object({}) do |path, objects|
|
86
|
-
file = Util.load_yaml_from_file(path, decorator: decorator)
|
87
|
-
objects[path.sub(%r{\A#{@root}/}, '')] = file
|
88
|
-
end
|
88
|
+
Util.load_objects_from_path(File.join(@root, path, '*.{yaml,yml}'), decorator: decorator)
|
89
89
|
end
|
90
90
|
end
|
91
91
|
end
|
data/lib/hippo/package.rb
CHANGED
data/lib/hippo/repository_tag.rb
CHANGED
@@ -30,8 +30,9 @@ module Hippo
|
|
30
30
|
return nil if @options['url'].nil?
|
31
31
|
|
32
32
|
@remote_refs ||= begin
|
33
|
-
|
34
|
-
|
33
|
+
Util.action "Getting remote refs from #{@options['url']}..." do
|
34
|
+
Git.ls_remote(@options['url'])
|
35
|
+
end
|
35
36
|
end
|
36
37
|
end
|
37
38
|
end
|
data/lib/hippo/secret_manager.rb
CHANGED
@@ -15,17 +15,7 @@ module Hippo
|
|
15
15
|
CIPHER = OpenSSL::Cipher.new('aes-256-gcm')
|
16
16
|
|
17
17
|
def path
|
18
|
-
File.join(@stage.
|
19
|
-
end
|
20
|
-
|
21
|
-
def secret(name)
|
22
|
-
Secret.new(self, name)
|
23
|
-
end
|
24
|
-
|
25
|
-
def secrets
|
26
|
-
Dir[File.join(root, '*.{yml,yaml}')].map do |path|
|
27
|
-
secret(path.split('/').last.sub(/\.ya?ml\z/, ''))
|
28
|
-
end
|
18
|
+
File.join(@stage.config_root, 'secrets.yaml')
|
29
19
|
end
|
30
20
|
|
31
21
|
# Download the current key from the Kubernetes API and set it as the
|
@@ -35,13 +25,24 @@ module Hippo
|
|
35
25
|
def download_key
|
36
26
|
return if @key
|
37
27
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
28
|
+
Util.action 'Downloading secret encryiption key...' do |state|
|
29
|
+
begin
|
30
|
+
value = @stage.get('secret', 'hippo-secret-key').first
|
31
|
+
|
32
|
+
if value.nil? || value.dig('data', 'key').nil?
|
33
|
+
state.call('not found')
|
34
|
+
return
|
35
|
+
end
|
36
|
+
|
37
|
+
@key = Base64.decode64(Base64.decode64(value['data']['key']))
|
38
|
+
rescue Hippo::Error => e
|
39
|
+
if e.message =~ /not found/
|
40
|
+
state.call('not found')
|
41
|
+
else
|
42
|
+
raise
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
45
46
|
end
|
46
47
|
|
47
48
|
# Is there a key availale in this manager?
|
@@ -118,7 +119,7 @@ module Hippo
|
|
118
119
|
|
119
120
|
return if exists?
|
120
121
|
|
121
|
-
yaml = { 'example' => 'This is an example
|
122
|
+
yaml = { 'example' => 'This is an example secret2!' }.to_yaml
|
122
123
|
FileUtils.mkdir_p(File.dirname(path))
|
123
124
|
File.open(path, 'w') { |f| f.write(encrypt(yaml)) }
|
124
125
|
end
|
@@ -130,9 +131,13 @@ module Hippo
|
|
130
131
|
raise Error, 'Cannot create edit file because no key is available for decryption'
|
131
132
|
end
|
132
133
|
|
133
|
-
|
134
|
-
|
135
|
-
|
134
|
+
old_contents = decrypt(File.read(path))
|
135
|
+
new_contents = Util.open_in_editor('secret', old_contents)
|
136
|
+
if old_contents != new_contents
|
137
|
+
write_file(new_contents)
|
138
|
+
else
|
139
|
+
puts 'No changes detected. Not re-encrypting secret file.'
|
140
|
+
end
|
136
141
|
end
|
137
142
|
|
138
143
|
def write_file(contents)
|