hippo-cli 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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