hippo-cli 1.1.0 → 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/bin/hippo +4 -4
- data/cli/apply_config.rb +1 -0
- data/cli/apply_services.rb +1 -0
- data/cli/console.rb +2 -0
- data/cli/create.rb +83 -0
- data/cli/deploy.rb +2 -0
- data/cli/init.rb +1 -1
- data/cli/install.rb +3 -1
- data/cli/key.rb +34 -0
- data/cli/kubectl.rb +2 -0
- data/cli/logs.rb +56 -0
- data/cli/objects.rb +50 -0
- data/cli/package_install.rb +30 -0
- data/cli/package_list.rb +40 -0
- data/cli/package_notes.rb +23 -0
- data/cli/package_test.rb +21 -0
- data/cli/package_uninstall.rb +27 -0
- data/cli/package_upgrade.rb +30 -0
- data/cli/package_values.rb +22 -0
- data/cli/prepare.rb +18 -0
- data/cli/run.rb +37 -0
- data/cli/secrets.rb +24 -0
- data/cli/stages.rb +26 -0
- data/cli/status.rb +25 -0
- data/cli/vars.rb +20 -0
- data/cli/version.rb +8 -0
- data/lib/hippo.rb +12 -0
- data/lib/hippo/bootstrap_parser.rb +64 -0
- data/lib/hippo/cli.rb +47 -14
- data/lib/hippo/extensions.rb +9 -0
- data/lib/hippo/image.rb +35 -31
- data/lib/hippo/manifest.rb +17 -7
- data/lib/hippo/object_definition.rb +18 -5
- data/lib/hippo/package.rb +124 -0
- data/lib/hippo/repository_tag.rb +38 -0
- data/lib/hippo/secret_manager.rb +61 -14
- data/lib/hippo/stage.rb +60 -26
- data/lib/hippo/util.rb +34 -0
- data/lib/hippo/version.rb +1 -1
- data/template/Hippofile +10 -2
- metadata +44 -13
- metadata.gz.sig +0 -0
- data/cli/secrets_edit.rb +0 -34
- data/cli/secrets_key.rb +0 -20
- data/lib/hippo/secret.rb +0 -112
- data/template/config/env-vars.yaml +0 -7
- data/template/deployments/web.yaml +0 -29
- data/template/deployments/worker.yaml +0 -26
- data/template/jobs/deploy/db-migration.yaml +0 -22
- data/template/jobs/install/load-schema.yaml +0 -22
- data/template/services/main.ingress.yaml +0 -13
- data/template/services/web.svc.yaml +0 -11
- data/template/stages/production.yaml +0 -7
data/lib/hippo/stage.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require 'liquid'
|
4
4
|
require 'open3'
|
5
5
|
require 'hippo/secret_manager'
|
6
|
+
require 'hippo/package'
|
6
7
|
|
7
8
|
module Hippo
|
8
9
|
class Stage
|
@@ -21,6 +22,10 @@ module Hippo
|
|
21
22
|
@options['branch']
|
22
23
|
end
|
23
24
|
|
25
|
+
def image_tag
|
26
|
+
@options['image-tag']
|
27
|
+
end
|
28
|
+
|
24
29
|
def namespace
|
25
30
|
@options['namespace']
|
26
31
|
end
|
@@ -29,31 +34,43 @@ module Hippo
|
|
29
34
|
@options['context']
|
30
35
|
end
|
31
36
|
|
32
|
-
def
|
33
|
-
@options['
|
37
|
+
def config
|
38
|
+
@options['config']
|
39
|
+
end
|
40
|
+
|
41
|
+
def images
|
42
|
+
@images ||= @manifest.images.deep_merge(@options['images'] || {}).each_with_object({}) do |(key, image), hash|
|
43
|
+
hash[key] = Image.new(key, image)
|
44
|
+
end
|
34
45
|
end
|
35
46
|
|
36
47
|
# These are the vars to represent this
|
37
48
|
def template_vars
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
49
|
+
@template_vars ||= begin
|
50
|
+
{
|
51
|
+
'manifest' => @manifest.template_vars,
|
52
|
+
'stage-name' => name,
|
53
|
+
'branch' => branch,
|
54
|
+
'image-tag' => image_tag,
|
55
|
+
'namespace' => namespace,
|
56
|
+
'context' => context,
|
57
|
+
'images' => images.values.each_with_object({}) { |image, hash| hash[image.name] = image.template_vars },
|
58
|
+
'config' => @manifest.config.deep_merge(config),
|
59
|
+
'secrets' => secret_manager.all
|
60
|
+
}
|
61
|
+
end
|
46
62
|
end
|
47
63
|
|
48
64
|
# Return a new decorator object that can be passed to objects that
|
49
65
|
# would like to decorator things.
|
50
66
|
def decorator
|
51
67
|
proc do |data|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
68
|
+
begin
|
69
|
+
template = Liquid::Template.parse(data)
|
70
|
+
template.render(template_vars)
|
71
|
+
rescue Liquid::SyntaxError => e
|
72
|
+
raise Error, "Template error: #{e.message}"
|
73
|
+
end
|
57
74
|
end
|
58
75
|
end
|
59
76
|
|
@@ -69,28 +86,46 @@ module Hippo
|
|
69
86
|
#
|
70
87
|
# @return [Hash<String,Hippo::ObjectDefinition>]
|
71
88
|
def deployments
|
72
|
-
Util.create_object_definitions(objects('deployments'), self, required_kinds: ['Deployment'])
|
89
|
+
@deployments ||= Util.create_object_definitions(objects('deployments'), self, required_kinds: ['Deployment'])
|
73
90
|
end
|
74
91
|
|
75
92
|
# Return an array of all services/ingresses for this stage
|
76
93
|
#
|
77
94
|
# @return [Hash<String,Hippo::ObjectDefinition>]
|
78
95
|
def services
|
79
|
-
Util.create_object_definitions(objects('services'), self, required_kinds: %w[Service Ingress NetworkPolicy])
|
96
|
+
@services ||= Util.create_object_definitions(objects('services'), self, required_kinds: %w[Service Ingress NetworkPolicy])
|
80
97
|
end
|
81
98
|
|
82
99
|
# Return an array of all configuration objects
|
83
100
|
#
|
84
101
|
# @return [Hash<String,Hippo::ObjectDefinition>]
|
85
102
|
def configs
|
86
|
-
Util.create_object_definitions(objects('config'), self)
|
103
|
+
@configs ||= Util.create_object_definitions(objects('config'), self)
|
87
104
|
end
|
88
105
|
|
89
106
|
# Return an array of all job objects
|
90
107
|
#
|
91
108
|
# @return [Hash<String,Hippo::ObjectDefinition>]
|
92
109
|
def jobs(type)
|
93
|
-
|
110
|
+
@jobs ||= {}
|
111
|
+
@jobs[type] ||= Util.create_object_definitions(objects("jobs/#{type}"), self)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Return a hash of all packages available in the stage
|
115
|
+
#
|
116
|
+
# @return [Hash<String, Hippo::Package>]
|
117
|
+
def packages
|
118
|
+
@packages ||= objects('packages').values.each_with_object({}) do |package_hash, hash|
|
119
|
+
package = Package.new(package_hash.first, self)
|
120
|
+
hash[package.name] = package
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Return any package values that have been defined
|
125
|
+
#
|
126
|
+
# @return [Hash]
|
127
|
+
def overridden_package_values
|
128
|
+
@options['packages'] || {}
|
94
129
|
end
|
95
130
|
|
96
131
|
# Return a kubectl command ready for use within this stage's
|
@@ -109,7 +144,7 @@ module Hippo
|
|
109
144
|
# @param objects [Array<Hippo::ObjectDefinition>]
|
110
145
|
# @return [Hash]
|
111
146
|
def apply(objects)
|
112
|
-
yaml_to_apply = objects.map(&:
|
147
|
+
yaml_to_apply = objects.map(&:yaml_to_apply).join("\n")
|
113
148
|
|
114
149
|
command = ['kubectl']
|
115
150
|
command += ['--context', context] if context
|
@@ -144,13 +179,12 @@ module Hippo
|
|
144
179
|
# @return [Array<Hippo::ObjectDefinition>]
|
145
180
|
def get(*names)
|
146
181
|
command = kubectl('get', '-o', 'yaml', *names)
|
147
|
-
Open3.
|
148
|
-
|
182
|
+
stdout, stderr, status = Open3.capture3(*command)
|
183
|
+
raise Error, "[kubectl] #{stderr}" unless status.success?
|
149
184
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
end
|
185
|
+
yaml = YAML.safe_load(stdout, permitted_classes: [Time])
|
186
|
+
yaml = yaml['items'] || [yaml]
|
187
|
+
yaml.map { |y| ObjectDefinition.new(y, self, clean: true) }
|
154
188
|
end
|
155
189
|
|
156
190
|
# Delete an object from the kubernetes API
|
data/lib/hippo/util.rb
CHANGED
@@ -68,6 +68,40 @@ module Hippo
|
|
68
68
|
tmpfile.unlink
|
69
69
|
end
|
70
70
|
end
|
71
|
+
|
72
|
+
def confirm(question)
|
73
|
+
response = ask(question)
|
74
|
+
if %w[yes y].include?(response.downcase)
|
75
|
+
puts
|
76
|
+
true
|
77
|
+
else
|
78
|
+
false
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def select(question, items)
|
83
|
+
items.each_with_index do |item, index|
|
84
|
+
puts "#{index + 1}) #{item}"
|
85
|
+
end
|
86
|
+
selected_item = nil
|
87
|
+
|
88
|
+
until selected_item
|
89
|
+
response = ask(question)
|
90
|
+
selected_item = items[response.to_i - 1]
|
91
|
+
if selected_item.nil?
|
92
|
+
puts "\e[31mThat is not a valid option. Try again.\e[0m"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
selected_item
|
97
|
+
end
|
98
|
+
|
99
|
+
def ask(question, default: nil)
|
100
|
+
puts "\e[35m#{question}\e[0m" + (default ? " [#{default}]" : '')
|
101
|
+
response = STDIN.gets
|
102
|
+
response = response.to_s.strip
|
103
|
+
response.empty? ? default : response
|
104
|
+
end
|
71
105
|
end
|
72
106
|
end
|
73
107
|
end
|
data/lib/hippo/version.rb
CHANGED
data/template/Hippofile
CHANGED
@@ -7,8 +7,16 @@ name: myapp
|
|
7
7
|
|
8
8
|
images:
|
9
9
|
main:
|
10
|
-
|
11
|
-
|
10
|
+
host: index.docker.io
|
11
|
+
name: myorg/myapp
|
12
|
+
tag: latest
|
13
|
+
# Alternatively, load the tag name from the current HEAD commit of a
|
14
|
+
# branch of a remote repository...
|
15
|
+
#
|
16
|
+
# tag:
|
17
|
+
# fromRepository:
|
18
|
+
# url: git@github.com:myorg/myapp
|
19
|
+
# branch: master
|
12
20
|
|
13
21
|
# If you wish, you can define a console command that allows you to easil
|
14
22
|
# open a console using `hippo [stage] console`
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hippo-cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adam Cooke
|
@@ -30,7 +30,7 @@ cert_chain:
|
|
30
30
|
3wUJNGnT5XYq+qvTqmjkTSTfdGvZCM63C6bGdN5CAyMokGOOatGqyCMAONolWnfC
|
31
31
|
gm3t2GWWrxY=
|
32
32
|
-----END CERTIFICATE-----
|
33
|
-
date: 2020-02-
|
33
|
+
date: 2020-02-10 00:00:00.000000000 Z
|
34
34
|
dependencies:
|
35
35
|
- !ruby/object:Gem::Dependency
|
36
36
|
name: encryptor
|
@@ -92,6 +92,26 @@ dependencies:
|
|
92
92
|
- - "<"
|
93
93
|
- !ruby/object:Gem::Version
|
94
94
|
version: '5.0'
|
95
|
+
- !ruby/object:Gem::Dependency
|
96
|
+
name: secure_random_string
|
97
|
+
requirement: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '1.0'
|
102
|
+
- - "<"
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '2.0'
|
105
|
+
type: :runtime
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '1.0'
|
112
|
+
- - "<"
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: '2.0'
|
95
115
|
- !ruby/object:Gem::Dependency
|
96
116
|
name: swamp-cli
|
97
117
|
requirement: !ruby/object:Gem::Requirement
|
@@ -124,34 +144,45 @@ files:
|
|
124
144
|
- cli/apply_config.rb
|
125
145
|
- cli/apply_services.rb
|
126
146
|
- cli/console.rb
|
147
|
+
- cli/create.rb
|
127
148
|
- cli/deploy.rb
|
128
149
|
- cli/help.rb
|
129
150
|
- cli/init.rb
|
130
151
|
- cli/install.rb
|
152
|
+
- cli/key.rb
|
131
153
|
- cli/kubectl.rb
|
132
|
-
- cli/
|
133
|
-
- cli/
|
154
|
+
- cli/logs.rb
|
155
|
+
- cli/objects.rb
|
156
|
+
- cli/package_install.rb
|
157
|
+
- cli/package_list.rb
|
158
|
+
- cli/package_notes.rb
|
159
|
+
- cli/package_test.rb
|
160
|
+
- cli/package_uninstall.rb
|
161
|
+
- cli/package_upgrade.rb
|
162
|
+
- cli/package_values.rb
|
163
|
+
- cli/prepare.rb
|
164
|
+
- cli/run.rb
|
165
|
+
- cli/secrets.rb
|
166
|
+
- cli/stages.rb
|
167
|
+
- cli/status.rb
|
168
|
+
- cli/vars.rb
|
169
|
+
- cli/version.rb
|
134
170
|
- lib/hippo.rb
|
171
|
+
- lib/hippo/bootstrap_parser.rb
|
135
172
|
- lib/hippo/cli.rb
|
136
173
|
- lib/hippo/deployment_monitor.rb
|
137
174
|
- lib/hippo/error.rb
|
175
|
+
- lib/hippo/extensions.rb
|
138
176
|
- lib/hippo/image.rb
|
139
177
|
- lib/hippo/manifest.rb
|
140
178
|
- lib/hippo/object_definition.rb
|
141
|
-
- lib/hippo/
|
179
|
+
- lib/hippo/package.rb
|
180
|
+
- lib/hippo/repository_tag.rb
|
142
181
|
- lib/hippo/secret_manager.rb
|
143
182
|
- lib/hippo/stage.rb
|
144
183
|
- lib/hippo/util.rb
|
145
184
|
- lib/hippo/version.rb
|
146
185
|
- template/Hippofile
|
147
|
-
- template/config/env-vars.yaml
|
148
|
-
- template/deployments/web.yaml
|
149
|
-
- template/deployments/worker.yaml
|
150
|
-
- template/jobs/deploy/db-migration.yaml
|
151
|
-
- template/jobs/install/load-schema.yaml
|
152
|
-
- template/services/main.ingress.yaml
|
153
|
-
- template/services/web.svc.yaml
|
154
|
-
- template/stages/production.yaml
|
155
186
|
homepage: https://github.com/adamcooke/hippo
|
156
187
|
licenses:
|
157
188
|
- MIT
|
metadata.gz.sig
CHANGED
Binary file
|
data/cli/secrets_edit.rb
DELETED
@@ -1,34 +0,0 @@
|
|
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
DELETED
@@ -1,20 +0,0 @@
|
|
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/secret.rb
DELETED
@@ -1,112 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Hippo
|
4
|
-
class Secret
|
5
|
-
HEADER = [
|
6
|
-
'# This file is encrypted and managed by Hippo.',
|
7
|
-
'# Use `hippo [stage] secrets:edit [name]` to make changes to it.',
|
8
|
-
'#',
|
9
|
-
'# Note: this cannot be applied directly to your Kubernetes server because',
|
10
|
-
'# HippoEncryptedSecret is not a valid object. It will be automatically ',
|
11
|
-
'# converted to a Secret when it is applied by Hippo.'
|
12
|
-
].join("\n")
|
13
|
-
|
14
|
-
EDIT_HEADER = [
|
15
|
-
'# This file has been unencrypted for you to edit it.',
|
16
|
-
'# Make your changes and close your edit to re-encrypt and save the file.',
|
17
|
-
'# You can change the apiVersion or add any additional metadata.',
|
18
|
-
'#',
|
19
|
-
'# You should not change the kind of document, it should be HippoEncryptedSecret.'
|
20
|
-
].join("\n")
|
21
|
-
|
22
|
-
def initialize(manager, name)
|
23
|
-
@manager = manager
|
24
|
-
@name = name
|
25
|
-
end
|
26
|
-
|
27
|
-
# Return the path where this secret is stored
|
28
|
-
#
|
29
|
-
# @return [String]
|
30
|
-
def path
|
31
|
-
File.join(@manager.root, "#{@name}.yaml")
|
32
|
-
end
|
33
|
-
|
34
|
-
# Does this secret exist yet?
|
35
|
-
#
|
36
|
-
# @return [Boolean]
|
37
|
-
def exists?
|
38
|
-
File.file?(path)
|
39
|
-
end
|
40
|
-
|
41
|
-
# Create a new empty secret file on the file system
|
42
|
-
#
|
43
|
-
# @return [void]
|
44
|
-
def create
|
45
|
-
return if exists?
|
46
|
-
|
47
|
-
od = ObjectDefinition.new(
|
48
|
-
{
|
49
|
-
'kind' => 'HippoEncryptedSecret',
|
50
|
-
'apiVersion' => 'v1',
|
51
|
-
'metadata' => {
|
52
|
-
'name' => @name
|
53
|
-
},
|
54
|
-
'data' => {
|
55
|
-
'example-value' => @manager.encrypt('This is an example encrypted value!')
|
56
|
-
}
|
57
|
-
},
|
58
|
-
@manager.stage,
|
59
|
-
clean: true
|
60
|
-
)
|
61
|
-
File.open(path, 'w') { |f| f.write(HEADER + "\n" + od.yaml) }
|
62
|
-
end
|
63
|
-
|
64
|
-
# Read the value from the file and decrypt all values that are present
|
65
|
-
#
|
66
|
-
# @return [String]
|
67
|
-
def editable_yaml
|
68
|
-
return unless exists?
|
69
|
-
|
70
|
-
objects = Util.load_yaml_from_file(path)
|
71
|
-
objects.each do |hash|
|
72
|
-
hash['data'].each do |key, value|
|
73
|
-
hash['data'][key] = @manager.decrypt(value)
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
objects.map(&:to_yaml).join("\n---\n")
|
78
|
-
end
|
79
|
-
|
80
|
-
# Edit this secret
|
81
|
-
#
|
82
|
-
# @return [void]
|
83
|
-
def edit
|
84
|
-
contents = Util.open_in_editor("secret-#{@name}", EDIT_HEADER + "\n" + editable_yaml)
|
85
|
-
yamls = Util.load_yaml_from_data(contents)
|
86
|
-
ods = Util.create_object_definitions({ 'secret' => yamls }, @manager.stage, required_kinds: ['HippoEncryptedSecret'], clean: true)
|
87
|
-
ods.each do |od|
|
88
|
-
od['data'].each do |key, value|
|
89
|
-
od['data'][key] = @manager.encrypt(value)
|
90
|
-
end
|
91
|
-
end
|
92
|
-
File.open(path, 'w') { |f| f.write(HEADER + "\n" + ods.map(&:yaml).join("\n---\n")) }
|
93
|
-
rescue StandardError => e
|
94
|
-
puts "Failed to edit secret (#{e.message})"
|
95
|
-
retry
|
96
|
-
end
|
97
|
-
|
98
|
-
# Return this secret as it can be exported to kubernetes
|
99
|
-
#
|
100
|
-
# @return [Array<ObjectDefinition>]
|
101
|
-
def applyable_yaml
|
102
|
-
objects = Util.load_yaml_from_file(path)
|
103
|
-
objects = objects.each do |hash|
|
104
|
-
hash['kind'] = 'Secret'
|
105
|
-
hash['data'].each do |key, value|
|
106
|
-
hash['data'][key] = Base64.encode64(@manager.decrypt(value)).gsub("\n", '')
|
107
|
-
end
|
108
|
-
end
|
109
|
-
Util.create_object_definitions({ 'secret' => objects }, @manager.stage)
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|