hippo-cli 1.0.1 → 1.1.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4d9636ec9f0d56f6cd5633d73bee77605fb30383c3bd5d793c6245db0720b30a
4
- data.tar.gz: d1dbc5b162c243d4cc1806e8d15ce5e7f59a7ad631d0ac75986e1003afcc5380
3
+ metadata.gz: 862958b0818bb34f9956e83e82738d1b59b2d4fba32c8bde858b9793129b0c9c
4
+ data.tar.gz: 9f3fd4e9587b882cb6588058b94efa85c8049bb31869b013c3a13f81edca5525
5
5
  SHA512:
6
- metadata.gz: 65da298f8f79ba3b5bf954e67283d8aa5340b0f02276c1c85110f7fcdba305dc64a8aee0075196a1bdf807476d9282ae3211174ad21c6d2708286b44784b87bc
7
- data.tar.gz: 9ec88a924598b71181da03ea899adec5472633d97eee8cae6c57bf30e8cf833df93458177ba096937aa7f2f834ac0b06f70051a041fae80af5fc661c0a0fa1ea
6
+ metadata.gz: f04805d52992d14804d21671dfbc59e1ea85dc28a3d490f135353f3cf6ba94830c1f01a0f5b1f16e3e8cabf2cd433a03e883ded0f7227f727abaa6ecf9d35f83
7
+ data.tar.gz: 6aed2eaa5a88fd0f9b99eaeca58de8f208eb72825bee8a7c4ef78068092ecf3e3a901ae0b9784e05a78d7785091d22ac1e4ac523897ca1f696eaeaf39da6eedb
Binary file
data.tar.gz.sig CHANGED
Binary file
@@ -8,10 +8,9 @@ command :'apply-config' do
8
8
  end
9
9
 
10
10
  action do |context|
11
- require 'hippo/cli_steps'
12
- steps = Hippo::CLISteps.setup(context)
13
- steps.apply_namespace
14
- steps.apply_config
15
- steps.apply_secrets
11
+ require 'hippo/cli'
12
+ cli = Hippo::CLI.setup(context)
13
+ cli.apply_namespace
14
+ cli.apply_config
16
15
  end
17
16
  end
@@ -8,8 +8,8 @@ command :'apply-services' do
8
8
  end
9
9
 
10
10
  action do |context|
11
- require 'hippo/cli_steps'
12
- steps = Hippo::CLISteps.setup(context)
13
- steps.apply_services
11
+ require 'hippo/cli'
12
+ cli = Hippo::CLI.setup(context)
13
+ cli.apply_services
14
14
  end
15
15
  end
@@ -16,16 +16,16 @@ command :console do
16
16
  end
17
17
 
18
18
  action do |context|
19
- require 'hippo/cli_steps'
20
- cli = Hippo::CLISteps.setup(context)
19
+ require 'hippo/cli'
20
+ cli = Hippo::CLI.setup(context)
21
21
 
22
- if cli.recipe.console.nil?
22
+ if cli.manifest.console.nil?
23
23
  raise Error, 'No console configuration has been provided in Hippofile'
24
24
  end
25
25
 
26
26
  time = Time.now.to_i
27
- deployment_name = context.options[:deployment] || cli.recipe.console['deployment']
28
- command = context.options[:command] || cli.recipe.console['command'] || 'bash'
29
- exec cli.stage.kubectl("exec -it deployment/#{deployment_name} -- #{command}")
27
+ deployment_name = context.options[:deployment] || cli.manifest.console['deployment']
28
+ command = context.options[:command] || cli.manifest.console['command'] || 'bash'
29
+ exec cli.stage.kubectl("exec -it deployment/#{deployment_name} -- #{command}").join(' ')
30
30
  end
31
31
  end
@@ -11,35 +11,24 @@ command :deploy do
11
11
  options[:jobs] = false
12
12
  end
13
13
 
14
- option '--no-build', 'Do not build the images' do |_value, options|
15
- options[:build] = false
16
- end
17
-
18
14
  action do |context|
19
- require 'hippo/cli_steps'
20
- steps = Hippo::CLISteps.setup(context)
21
- if context.options[:build] == false
22
- commit = steps.recipe.repository.commit_for_branch(steps.stage.branch)
23
- puts 'Not building an image and just hoping one exists for current commit.'
24
- puts "Using #{commit.objectish} from #{steps.stage.branch}"
25
- steps.prepare_repository(fetch: false)
26
- else
27
- steps.prepare_repository
28
- steps.build
29
- steps.publish
30
- end
15
+ require 'hippo/cli'
16
+ cli = Hippo::CLI.setup(context)
17
+ cli.verify_image_existence
31
18
 
32
- steps.apply_namespace
33
- steps.apply_config
34
- steps.apply_secrets
19
+ cli.apply_namespace
20
+ cli.apply_config
35
21
 
36
22
  unless context.options[:jobs] == false
37
- if steps.run_deploy_jobs == false
23
+ if cli.run_deploy_jobs == false
38
24
  raise Hippo::Error, 'Not all jobs completed successfully. Cannot continue with deployment.'
39
25
  end
40
26
  end
41
27
 
42
- steps.apply_services
43
- steps.deploy
28
+ unless cli.deploy
29
+ puts 'Deployment did not complete successfully. Not continuing any further.'
30
+ exit 2
31
+ end
32
+ cli.apply_services
44
33
  end
45
34
  end
@@ -7,38 +7,37 @@ command :install do
7
7
  options[:hippofile] = value.to_s
8
8
  end
9
9
 
10
- option '--no-build', 'Do not build the images' do |_value, options|
11
- options[:build] = false
12
- end
13
-
14
10
  option '--no-deploy', 'Do not deploy after install' do |_value, options|
15
11
  options[:deploy] = false
16
12
  end
17
13
 
14
+ option '--no-jobs', 'Do not run the deploy jobs' do |_value, options|
15
+ options[:jobs] = false
16
+ end
17
+
18
18
  action do |context|
19
- require 'hippo/cli_steps'
20
- steps = Hippo::CLISteps.setup(context)
21
- if context.options[:build] == false
22
- commit = steps.recipe.repository.commit_for_branch(steps.stage.branch)
23
- puts 'Not building an image and just hoping one exists for current commit.'
24
- steps.prepare_repository(fetch: false)
25
- else
26
- steps.prepare_repository
27
- steps.build
28
- steps.publish
29
- end
19
+ require 'hippo/cli'
20
+ cli = Hippo::CLI.setup(context)
21
+ cli.verify_image_existence
22
+
23
+ cli.apply_namespace
24
+ cli.apply_config
30
25
 
31
- steps.apply_namespace
32
- steps.apply_config
33
- steps.apply_secrets
26
+ unless context.options[:jobs] == false
27
+ if cli.run_install_jobs == false
28
+ raise Hippo::Error, 'Not all jobs completed successfully. Cannot continue with installation.'
29
+ end
30
+ end
34
31
 
35
- if steps.run_install_jobs == false
36
- raise Hippo::Error, 'Not all installation jobs completed successfully. Cannot continue to deploy.'
32
+ if options[:deploy] == false
33
+ puts 'Not deploying because --no-deploy was specified'
34
+ exit 0
37
35
  end
38
36
 
39
- unless context.options[:deploy] == false
40
- steps.apply_services
41
- steps.deploy
37
+ unless cli.deploy
38
+ puts 'Deployment did not complete successfully. Not continuing any further.'
39
+ exit 2
42
40
  end
41
+ cli.apply_services
43
42
  end
44
43
  end
@@ -8,10 +8,10 @@ command :kubectl do
8
8
  end
9
9
 
10
10
  action do |context|
11
- require 'hippo/cli_steps'
12
- cli = Hippo::CLISteps.setup(context)
11
+ require 'hippo/cli'
12
+ cli = Hippo::CLI.setup(context)
13
13
  ARGV.shift(2)
14
14
  ARGV.delete('--')
15
- exec cli.stage.kubectl(*ARGV)
15
+ exec cli.stage.kubectl(*ARGV).join(' ')
16
16
  end
17
17
  end
@@ -0,0 +1,34 @@
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
@@ -0,0 +1,20 @@
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
@@ -1,7 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Hippo
4
+ # The path where the user configuration file is stored
5
+ CONFIG_PATH = File.join(ENV['HOME'], '.hippo', 'config.yaml')
6
+
7
+ # Return the root to the gem
8
+ #
9
+ # @return [String]
4
10
  def self.root
5
11
  File.expand_path('../', __dir__)
6
12
  end
13
+
14
+ # User the user configuration for Hippo
15
+ #
16
+ # @return [Hash]
17
+ def self.config
18
+ @config ||= begin
19
+ if File.file?(CONFIG_PATH)
20
+ YAML.load_file(CONFIG_PATH)
21
+ else
22
+ {}
23
+ end
24
+ end
25
+ end
7
26
  end
@@ -0,0 +1,210 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+ require 'hippo/manifest'
5
+ require 'hippo/deployment_monitor'
6
+
7
+ module Hippo
8
+ class CLI
9
+ attr_reader :manifest
10
+ attr_reader :stage
11
+
12
+ # Initialize a new CLI instance
13
+ #
14
+ # @param manifest [Hippo::Manifest]
15
+ # @param stage [Hippo::Stage]
16
+ # @return [Hippo::CLI]
17
+ def initialize(manifest, stage)
18
+ @manifest = manifest
19
+ @stage = stage
20
+ end
21
+
22
+ # Verify image existence
23
+ #
24
+ # @return [void]
25
+ def verify_image_existence
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})"
31
+ else
32
+ missing += 1
33
+ puts "No #{image.name} image at #{image.url} (with tag #{commit})"
34
+ end
35
+ end
36
+
37
+ if missing > 0
38
+ raise Error, "#{missing} #{missing == 1 ? 'image was' : 'images were'} not available. Cannot continue."
39
+ end
40
+ end
41
+
42
+ # Apply the namespace configuration
43
+ #
44
+ # @return [void]
45
+ def apply_namespace
46
+ od = Hippo::ObjectDefinition.new(
47
+ {
48
+ 'kind' => 'Namespace',
49
+ 'apiVersion' => 'v1',
50
+ 'metadata' => { 'name' => @stage.namespace, 'labels' => { 'name' => @stage.namespace } }
51
+ },
52
+ @stage
53
+ )
54
+ apply([od], 'namespace')
55
+ end
56
+
57
+ # Apply all configuration and secrets
58
+ #
59
+ # @return [void]
60
+ def apply_config
61
+ apply(@stage.configs, 'configuration')
62
+
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.'
68
+ end
69
+ end
70
+
71
+ # Apply all services, ingresses and policies
72
+ #
73
+ # @return [void]
74
+ def apply_services
75
+ apply(@stage.services, 'service')
76
+ end
77
+
78
+ # Run all deploy jobs
79
+ #
80
+ # @return [void]
81
+ def run_deploy_jobs
82
+ run_jobs('deploy')
83
+ end
84
+
85
+ # Run all install jobs
86
+ #
87
+ # @return [void]
88
+ def run_install_jobs
89
+ run_jobs('install')
90
+ end
91
+
92
+ # Run a full deployment
93
+ #
94
+ # @return [void]
95
+ def deploy
96
+ deployment_id = SecureRandom.hex(6)
97
+ deployments = @stage.deployments
98
+ if deployments.empty?
99
+ puts 'There are no deployment objects defined.'
100
+ return true
101
+ end
102
+
103
+ puts "Using deployment ID: #{deployment_id}"
104
+
105
+ deployments.each do |deployment|
106
+ deployment.insert_deployment_id!(deployment_id)
107
+ end
108
+
109
+ apply(deployments, 'deployment')
110
+ puts 'Waiting for all deployments to roll out...'
111
+
112
+ monitor = DeploymentMonitor.new(@stage, deployment_id)
113
+ monitor.on_success do |poll|
114
+ if poll.replica_sets.size == 1
115
+ puts "\e[32mDeployment rolled out successfully\e[0m"
116
+ else
117
+ puts "\e[32mAll #{poll.replica_sets.size} deployments all rolled out successfully\e[0m"
118
+ end
119
+ end
120
+
121
+ monitor.on_wait do |poll|
122
+ puts "Waiting for #{poll.pending.size} #{poll.pending.size == 1 ? 'deployment' : 'deployments'} (#{poll.pending_names.join(', ')})"
123
+ end
124
+
125
+ monitor.on_failure do |poll|
126
+ puts "\e[31mLooks like things aren't going to plan with some deployments.\e[0m"
127
+ puts 'You can review potential issues using the commads below:'
128
+
129
+ poll.pending.each do |rs|
130
+ puts
131
+ name = rs.name.split('-').first
132
+ puts " hippo #{@stage.name} kubectl -- describe deployment \e[35m#{name}\e[0m"
133
+ puts " hippo #{@stage.name} kubectl -- logs deployment/\e[35m#{name}\e[0m --all-containers"
134
+ end
135
+ puts
136
+ end
137
+
138
+ monitor.wait
139
+ end
140
+
141
+ private
142
+
143
+ def apply(objects, type)
144
+ puts "Applying #{objects.size} #{type} #{objects.size == 1 ? 'object' : 'objects'}"
145
+ @stage.apply(objects)
146
+ end
147
+
148
+ def run_jobs(type)
149
+ puts "Running #{type} jobs"
150
+ jobs = @stage.jobs(type)
151
+ if jobs.empty?
152
+ puts "There are no #{type} jobs to run"
153
+ return true
154
+ end
155
+
156
+ jobs.each do |job|
157
+ @stage.delete('job', job.name)
158
+ end
159
+
160
+ applied_jobs = apply(jobs, 'deploy job')
161
+
162
+ timeout, jobs = @stage.wait_for_jobs(applied_jobs.keys)
163
+ success_jobs = []
164
+ failed_jobs = []
165
+ jobs.each do |job|
166
+ if job['status']['succeeded']
167
+ success_jobs << job
168
+ else
169
+ failed_jobs << job
170
+ end
171
+ end
172
+
173
+ if success_jobs.size == jobs.size
174
+ puts 'All jobs completed successfully'
175
+ puts 'You can review the logs for these by running the commands below'
176
+ puts
177
+ result = true
178
+ else
179
+ puts "\e[31mNot all install jobs completed successfully.\e[0m"
180
+ puts 'You should review the logs for these using the commands below'
181
+ puts
182
+ result = false
183
+ end
184
+
185
+ jobs.each do |job|
186
+ icon = if job['status']['succeeded']
187
+ '✅'
188
+ else
189
+ '❌'
190
+ end
191
+ puts " #{icon} " + @stage.kubectl("logs job/#{job.name}").join(' ')
192
+ end
193
+ puts
194
+ result
195
+ end
196
+
197
+ class << self
198
+ def setup(context)
199
+ manifest = Hippo::Manifest.load_from_file(context.options[:hippofile] || './Hippofile')
200
+
201
+ stage = manifest.stages[CURRENT_STAGE]
202
+ if stage.nil?
203
+ raise Error, "Invalid stage name `#{CURRENT_STAGE}`. Check this has been defined in in your stages directory with a matching name?"
204
+ end
205
+
206
+ new(manifest, stage)
207
+ end
208
+ end
209
+ end
210
+ end