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