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.
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