hippo-cli 1.1.0 → 1.1.1

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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/bin/hippo +4 -4
  5. data/cli/apply_config.rb +1 -0
  6. data/cli/apply_services.rb +1 -0
  7. data/cli/console.rb +2 -0
  8. data/cli/create.rb +83 -0
  9. data/cli/deploy.rb +2 -0
  10. data/cli/init.rb +1 -1
  11. data/cli/install.rb +3 -1
  12. data/cli/key.rb +34 -0
  13. data/cli/kubectl.rb +2 -0
  14. data/cli/logs.rb +56 -0
  15. data/cli/objects.rb +50 -0
  16. data/cli/package_install.rb +30 -0
  17. data/cli/package_list.rb +40 -0
  18. data/cli/package_notes.rb +23 -0
  19. data/cli/package_test.rb +21 -0
  20. data/cli/package_uninstall.rb +27 -0
  21. data/cli/package_upgrade.rb +30 -0
  22. data/cli/package_values.rb +22 -0
  23. data/cli/prepare.rb +18 -0
  24. data/cli/run.rb +37 -0
  25. data/cli/secrets.rb +24 -0
  26. data/cli/stages.rb +26 -0
  27. data/cli/status.rb +25 -0
  28. data/cli/vars.rb +20 -0
  29. data/cli/version.rb +8 -0
  30. data/lib/hippo.rb +12 -0
  31. data/lib/hippo/bootstrap_parser.rb +64 -0
  32. data/lib/hippo/cli.rb +47 -14
  33. data/lib/hippo/extensions.rb +9 -0
  34. data/lib/hippo/image.rb +35 -31
  35. data/lib/hippo/manifest.rb +17 -7
  36. data/lib/hippo/object_definition.rb +18 -5
  37. data/lib/hippo/package.rb +124 -0
  38. data/lib/hippo/repository_tag.rb +38 -0
  39. data/lib/hippo/secret_manager.rb +61 -14
  40. data/lib/hippo/stage.rb +60 -26
  41. data/lib/hippo/util.rb +34 -0
  42. data/lib/hippo/version.rb +1 -1
  43. data/template/Hippofile +10 -2
  44. metadata +44 -13
  45. metadata.gz.sig +0 -0
  46. data/cli/secrets_edit.rb +0 -34
  47. data/cli/secrets_key.rb +0 -20
  48. data/lib/hippo/secret.rb +0 -112
  49. data/template/config/env-vars.yaml +0 -7
  50. data/template/deployments/web.yaml +0 -29
  51. data/template/deployments/worker.yaml +0 -26
  52. data/template/jobs/deploy/db-migration.yaml +0 -22
  53. data/template/jobs/install/load-schema.yaml +0 -22
  54. data/template/services/main.ingress.yaml +0 -13
  55. data/template/services/web.svc.yaml +0 -11
  56. 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 vars
33
- @options['vars']
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
- 'name' => name,
40
- 'branch' => branch,
41
- 'namespace' => namespace,
42
- 'context' => context,
43
- 'images' => @manifest.images.values.each_with_object({}) { |image, hash| hash[image.name] = image.image_path_for_branch(branch) },
44
- 'vars' => vars
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
- template = Liquid::Template.parse(data)
53
- template.render(
54
- 'stage' => template_vars,
55
- 'manifest' => @manifest.template_vars
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
- Util.create_object_definitions(objects("jobs/#{type}"), self)
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(&:yaml).join("\n")
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.popen3(*command) do |_, stdout, stderr, wt|
148
- raise Error, "[kutectl] #{stderr.read}" unless wt.value.success?
182
+ stdout, stderr, status = Open3.capture3(*command)
183
+ raise Error, "[kubectl] #{stderr}" unless status.success?
149
184
 
150
- yaml = YAML.safe_load(stdout.read, permitted_classes: [Time])
151
- yaml = yaml['items'] || [yaml]
152
- yaml.map { |y| ObjectDefinition.new(y, self, clean: true) }
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Hippo
4
- VERSION = '1.1.0'
4
+ VERSION = '1.1.1'
5
5
  end
data/template/Hippofile CHANGED
@@ -7,8 +7,16 @@ name: myapp
7
7
 
8
8
  images:
9
9
  main:
10
- repository: git@github.com:myorg/myapp
11
- url: myorg/myapp
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.0
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-01 00:00:00.000000000 Z
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/secrets_edit.rb
133
- - cli/secrets_key.rb
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/secret.rb
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