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
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ command :'package:test' do
4
+ desc 'Test a package installation'
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
+ option '-p', '--package [NAME]', 'The name of the package' do |value, options|
11
+ options[:package] = value
12
+ end
13
+
14
+ action do |context|
15
+ require 'hippo/package'
16
+ package, cli = Hippo::Package.setup_from_cli_context(context)
17
+ cli.preflight
18
+
19
+ exec(*package.helm('test', package.name, '--logs'))
20
+ end
21
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ command :'package:uninstall' do
4
+ desc 'Uninstall a package'
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
+ option '-p', '--package [NAME]', 'The name of the package' do |value, options|
11
+ options[:package] = value
12
+ end
13
+
14
+ action do |context|
15
+ require 'hippo/package'
16
+ package, cli = Hippo::Package.setup_from_cli_context(context)
17
+ cli.preflight
18
+
19
+ if package.installed?
20
+ puts "Uninstalling #{package.name} with Helm..."
21
+ package.uninstall
22
+ puts "#{package.name} uninstalled successfully"
23
+ else
24
+ puts "#{package.name} is not installed."
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ command :'package:upgrade' do
4
+ desc 'Upgrade a package'
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
+ option '-p', '--package [NAME]', 'The name of the package' do |value, options|
11
+ options[:package] = value
12
+ end
13
+
14
+ action do |context|
15
+ require 'hippo/package'
16
+ package, cli = Hippo::Package.setup_from_cli_context(context)
17
+ cli.preflight
18
+
19
+ if package.installed?
20
+ cli.apply_namespace
21
+ cli.apply_config
22
+
23
+ puts "Upgrading #{package.name} with Helm..."
24
+ package.upgrade
25
+ puts "#{package.name} upgraded successfully"
26
+ else
27
+ puts "#{package.name} is not installed. You probably want to use package:install first."
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ command :'package:values' do
4
+ desc 'Display the values file that will be used for all packages'
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
+ action do |context|
11
+ require 'hippo/cli'
12
+ cli = Hippo::CLI.setup(context)
13
+ cli.preflight
14
+
15
+ cli.stage.packages.values.each do |package|
16
+ puts "\e[33m#{'=' * 80}"
17
+ puts package.name
18
+ puts "#{'=' * 80}\e[0m"
19
+ puts package.final_values.to_yaml.sub(/\A---\n/, '')
20
+ end
21
+ end
22
+ end
data/cli/prepare.rb ADDED
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ command :prepare do
4
+ desc 'Prepare Kubernetes namespace (including installing all packages)'
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
+ action do |context|
11
+ require 'hippo/cli'
12
+ cli = Hippo::CLI.setup(context)
13
+ cli.preflight
14
+ cli.apply_namespace
15
+ cli.apply_config
16
+ cli.install_all_packages
17
+ end
18
+ end
data/cli/run.rb ADDED
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ command :run do
4
+ desc 'Create and run a pod using the given image'
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
+ option '--command [COMMAND]', 'The command to run (defaults to /bin/bash)' do |value, options|
11
+ options[:command] = value.to_s
12
+ end
13
+
14
+ action do |context|
15
+ require 'hippo/cli'
16
+ cli = Hippo::CLI.setup(context)
17
+ cli.preflight
18
+
19
+ image = cli.stage.images.values.first
20
+ raise Error, "No image exists at #{image.image_url}" unless image.exists?
21
+
22
+ command = context.options[:command] || '/bin/bash'
23
+
24
+ pod_name = 'hp-run-' + SecureRandom.hex(4)
25
+ kubectl_command = cli.stage.kubectl(
26
+ 'run', pod_name,
27
+ '--restart', 'Never',
28
+ '--rm',
29
+ '--attach',
30
+ '-it',
31
+ '--image', image.image_url,
32
+ '--command', '--', command
33
+ )
34
+ puts "Starting pod #{pod_name} with #{image.image_url}"
35
+ exec *kubectl_command
36
+ end
37
+ end
data/cli/secrets.rb ADDED
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ command :secrets do
4
+ desc 'Create/edit an encrypted secrets file'
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
+ action do |context|
11
+ require 'hippo/cli'
12
+ cli = Hippo::CLI.setup(context)
13
+ cli.preflight
14
+
15
+ manager = cli.stage.secret_manager
16
+ unless manager.key_available?
17
+ puts "\e[31mNo key has been published for this stage yet.\e[0m"
18
+ puts "Use `hippo #{cli.stage.name} key --generate` to generate one."
19
+ exit 2
20
+ end
21
+
22
+ manager.edit
23
+ end
24
+ end
data/cli/stages.rb ADDED
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ command :stages do
4
+ desc 'List all stages that are available'
5
+ option '-h', '--hippofile [RECIPE]', 'The path to the Hippofile (defaults: ./Hippofile)' do |value, options|
6
+ options[:hippofile] = value.to_s
7
+ end
8
+
9
+ action do |context|
10
+ require 'hippo/manifest'
11
+ manifest = Hippo::Manifest.load_from_file(context.options[:hippofile] || './Hippofile')
12
+
13
+ if manifest.stages.empty?
14
+ puts 'There are no stages configured yet.'
15
+ puts 'Use the following command to create one:'
16
+ puts
17
+ puts ' hippo [name] create'
18
+ puts
19
+ exit 0
20
+ end
21
+
22
+ manifest.stages.each do |_, stage|
23
+ puts "- #{stage.name}"
24
+ end
25
+ end
26
+ end
data/cli/status.rb ADDED
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ command :status do
4
+ desc 'Show current status of the namespace'
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
+ option '--full', 'Include all relevant objects in namespace' do |_value, options|
11
+ options[:full] = true
12
+ end
13
+
14
+ action do |context|
15
+ require 'hippo/cli'
16
+ cli = Hippo::CLI.setup(context)
17
+ cli.preflight
18
+
19
+ objects = %w[pods svc ingress deployments jobs statefulset]
20
+ objects += %w[secret cm pvc networkpolicy] if context.options[:full]
21
+
22
+ command = cli.stage.kubectl('get', objects.join(','))
23
+ exec *command
24
+ end
25
+ end
data/cli/vars.rb ADDED
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ command :vars do
4
+ desc 'Show all variables available for use in this stage'
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
+ action do |context|
11
+ require 'hippo/cli'
12
+ cli = Hippo::CLI.setup(context)
13
+ cli.preflight
14
+
15
+ hash = cli.stage.template_vars.to_yaml.gsub(/^(\s*[\w\-]+)\:(.*)/) do
16
+ "\e[32m#{Regexp.last_match(1)}:\e[0m" + Regexp.last_match(2)
17
+ end
18
+ puts hash
19
+ end
20
+ end
data/cli/version.rb ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ command :version do
4
+ desc 'Print current Hippo version'
5
+ action do
6
+ puts 'Hippo v' + Hippo::VERSION
7
+ end
8
+ end
data/lib/hippo.rb CHANGED
@@ -23,4 +23,16 @@ module Hippo
23
23
  end
24
24
  end
25
25
  end
26
+
27
+ # Return the current kubectl context
28
+ #
29
+ # @return [String]
30
+ def self.current_kubectl_context
31
+ stdout, stderr, status = Open3.capture3('kubectl config current-context')
32
+ unless status.success?
33
+ raise Error, 'Could not determine current kubectl context'
34
+ end
35
+
36
+ stdout.strip
37
+ end
26
38
  end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+ require 'secure_random_string'
5
+
6
+ module Hippo
7
+ class BootstrapParser
8
+ def self.parse(source)
9
+ new(source).parse
10
+ end
11
+
12
+ TYPES = %w[placeholder password].freeze
13
+
14
+ def initialize(source)
15
+ @source = source || {}
16
+ end
17
+
18
+ def parse
19
+ parse_hash(@source)
20
+ end
21
+
22
+ private
23
+
24
+ def parse_hash(hash)
25
+ hash.each_with_object({}) do |(key, value), hash|
26
+ new_key = key.sub(/\A_/, '')
27
+ hash[new_key] = if value.is_a?(Hash) && key[0] == '_'
28
+ parse_generator(value)
29
+ elsif value.is_a?(Hash)
30
+ parse_hash(value)
31
+ else
32
+ value.to_s
33
+ end
34
+ end
35
+ end
36
+
37
+ def parse_generator(value)
38
+ case value['type']
39
+ when 'password'
40
+ password = SecureRandomString.new(value['length'] || 24).to_s
41
+ if value['addHashes']
42
+ {
43
+ 'plain' => password,
44
+ 'sha1' => Digest::SHA1.hexdigest(password),
45
+ 'sha2' => Digest::SHA2.hexdigest(password),
46
+ 'sha256' => Digest::SHA256.hexdigest(password)
47
+ }
48
+ else
49
+ password
50
+ end
51
+ when 'placeholder'
52
+ value['prefix'].to_s + 'xxx' + value['suffix'].to_s
53
+ when 'hex'
54
+ SecureRandom.hex(value['size'] ? value['size'].to_i : 16)
55
+ when 'random'
56
+ Base64.encode64(SecureRandom.random_bytes(value['size'] ? value['size'].to_i : 16)).strip
57
+ when nil
58
+ raise Error, "A 'type' must be provided for each generated item"
59
+ else
60
+ raise Error, "Invalid generator type #{value['type']}"
61
+ end
62
+ end
63
+ end
64
+ end
data/lib/hippo/cli.rb CHANGED
@@ -24,13 +24,12 @@ module Hippo
24
24
  # @return [void]
25
25
  def verify_image_existence
26
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})"
27
+ @stage.images.each do |_, image|
28
+ if image.exists?
29
+ puts "Image for #{image.name} exists at #{image.image_url}"
31
30
  else
32
31
  missing += 1
33
- puts "No #{image.name} image at #{image.url} (with tag #{commit})"
32
+ puts "No #{image.name} image at #{image.image_url}"
34
33
  end
35
34
  end
36
35
 
@@ -39,6 +38,21 @@ module Hippo
39
38
  end
40
39
  end
41
40
 
41
+ # Run any checks that should be performed before running
42
+ # any optionation which might influence the environment
43
+ #
44
+ # @return [void]
45
+ def preflight
46
+ if @stage.context.nil?
47
+ puts "\e[33mStage does not specify a context. The current context specified"
48
+ puts "by the kubectl config will be used (#{Hippo.current_kubectl_context}).\e[0m"
49
+ puts
50
+ puts 'This is not recommended. Add a context name to your stage configuration.'
51
+ puts
52
+ exit 0 unless Util.confirm('Do you wish to continue?')
53
+ end
54
+ end
55
+
42
56
  # Apply the namespace configuration
43
57
  #
44
58
  # @return [void]
@@ -59,13 +73,28 @@ module Hippo
59
73
  # @return [void]
60
74
  def apply_config
61
75
  apply(@stage.configs, 'configuration')
76
+ end
62
77
 
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.'
78
+ # Install all packages
79
+ #
80
+ # @return [void]
81
+ def install_all_packages
82
+ if @stage.packages.empty?
83
+ puts 'There are no packages to install'
84
+ return
68
85
  end
86
+
87
+ @stage.packages.values.each do |package|
88
+ if package.installed?
89
+ puts "#{package.name} is already installed. Upgrading..."
90
+ package.upgrade
91
+ else
92
+ puts "Installing #{package.name} using Helm..."
93
+ package.install
94
+ end
95
+ end
96
+
97
+ puts "Finished with #{@stage.packages.size} #{@stage.packages.size == 1 ? 'package' : 'packages'}"
69
98
  end
70
99
 
71
100
  # Apply all services, ingresses and policies
@@ -96,7 +125,7 @@ module Hippo
96
125
  deployment_id = SecureRandom.hex(6)
97
126
  deployments = @stage.deployments
98
127
  if deployments.empty?
99
- puts 'There are no deployment objects defined.'
128
+ puts 'There are no deployment objects defined'
100
129
  return true
101
130
  end
102
131
 
@@ -141,8 +170,12 @@ module Hippo
141
170
  private
142
171
 
143
172
  def apply(objects, type)
144
- puts "Applying #{objects.size} #{type} #{objects.size == 1 ? 'object' : 'objects'}"
145
- @stage.apply(objects)
173
+ if objects.empty?
174
+ puts "No #{type} objects found to apply"
175
+ else
176
+ puts "Applying #{objects.size} #{type} #{objects.size == 1 ? 'object' : 'objects'}"
177
+ @stage.apply(objects)
178
+ end
146
179
  end
147
180
 
148
181
  def run_jobs(type)
@@ -188,7 +221,7 @@ module Hippo
188
221
  else
189
222
  '❌'
190
223
  end
191
- puts " #{icon} " + @stage.kubectl("logs job/#{job.name}").join(' ')
224
+ puts " #{icon} hippo #{@stage.name} kubectl -- logs job/#{job.name}"
192
225
  end
193
226
  puts
194
227
  result