kontena-cli 0.16.3 → 0.17.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.dockerignore +1 -0
- data/.gitignore +3 -1
- data/VERSION +1 -1
- data/lib/kontena/callbacks/master/deploy/40_install_ssl_certificate_after_deploy.rb +32 -0
- data/lib/kontena/cli/apps/deploy_command.rb +2 -2
- data/lib/kontena/cli/apps/scale_command.rb +2 -2
- data/lib/kontena/cli/apps/show_command.rb +3 -2
- data/lib/kontena/cli/apps/yaml/validations.rb +10 -6
- data/lib/kontena/cli/apps/yaml/validator.rb +1 -0
- data/lib/kontena/cli/apps/yaml/validator_v2.rb +1 -0
- data/lib/kontena/cli/cloud/login_command.rb +66 -64
- data/lib/kontena/cli/common.rb +0 -10
- data/lib/kontena/cli/grids/logs_command.rb +0 -1
- data/lib/kontena/cli/localhost_web_server.rb +11 -3
- data/lib/kontena/cli/master/login_command.rb +213 -163
- data/lib/kontena/cli/nodes/label_command.rb +2 -0
- data/lib/kontena/cli/nodes/labels/add_command.rb +7 -8
- data/lib/kontena/cli/nodes/labels/list_command.rb +17 -0
- data/lib/kontena/cli/nodes/labels/remove_command.rb +7 -12
- data/lib/kontena/cli/nodes/show_command.rb +1 -0
- data/lib/kontena/cli/plugins/common.rb +8 -0
- data/lib/kontena/cli/plugins/install_command.rb +21 -2
- data/lib/kontena/cli/plugins/list_command.rb +4 -2
- data/lib/kontena/cli/plugins/search_command.rb +4 -2
- data/lib/kontena/cli/registry/create_command.rb +19 -12
- data/lib/kontena/cli/registry/remove_command.rb +4 -4
- data/lib/kontena/cli/registry_command.rb +0 -1
- data/lib/kontena/cli/services/create_command.rb +6 -6
- data/lib/kontena/cli/services/deploy_command.rb +8 -4
- data/lib/kontena/cli/services/list_command.rb +34 -21
- data/lib/kontena/cli/services/logs_command.rb +1 -1
- data/lib/kontena/cli/services/scale_command.rb +3 -3
- data/lib/kontena/cli/services/services_helper.rb +18 -14
- data/lib/kontena/cli/services/show_command.rb +1 -0
- data/lib/kontena/cli/services/update_command.rb +6 -6
- data/lib/kontena/cli/stack_command.rb +12 -6
- data/lib/kontena/cli/stacks/build_command.rb +110 -0
- data/lib/kontena/cli/stacks/common.rb +85 -20
- data/lib/kontena/cli/stacks/deploy_command.rb +30 -7
- data/lib/kontena/cli/stacks/install_command.rb +30 -0
- data/lib/kontena/cli/stacks/list_command.rb +74 -14
- data/lib/kontena/cli/stacks/logs_command.rb +31 -0
- data/lib/kontena/cli/stacks/monitor_command.rb +91 -0
- data/lib/kontena/cli/stacks/remove_command.rb +24 -7
- data/lib/kontena/cli/stacks/service_generator.rb +115 -0
- data/lib/kontena/cli/stacks/service_generator_v2.rb +27 -0
- data/lib/kontena/cli/stacks/show_command.rb +65 -13
- data/lib/kontena/cli/stacks/upgrade_command.rb +28 -0
- data/lib/kontena/cli/stacks/yaml/custom_validators/affinities_validator.rb +19 -0
- data/lib/kontena/cli/stacks/yaml/custom_validators/build_validator.rb +22 -0
- data/lib/kontena/cli/stacks/yaml/custom_validators/extends_validator.rb +21 -0
- data/lib/kontena/cli/stacks/yaml/custom_validators/hooks_validator.rb +54 -0
- data/lib/kontena/cli/stacks/yaml/custom_validators/secrets_validator.rb +22 -0
- data/lib/kontena/cli/stacks/yaml/reader.rb +219 -0
- data/lib/kontena/cli/stacks/yaml/service_extender.rb +78 -0
- data/lib/kontena/cli/stacks/yaml/validations.rb +71 -0
- data/lib/kontena/cli/stacks/yaml/validator_v3.rb +52 -0
- data/lib/kontena/cli/version_command.rb +5 -1
- data/lib/kontena/cli/vpn/create_command.rb +20 -17
- data/lib/kontena/cli/vpn/remove_command.rb +4 -3
- data/lib/kontena/client.rb +21 -20
- data/lib/kontena/machine/cert_helper.rb +4 -0
- data/lib/kontena/machine/cloud_config/cloudinit.yml +1 -1
- data/lib/kontena/main_command.rb +1 -1
- data/spec/fixtures/kontena-build.yml +2 -2
- data/spec/fixtures/kontena-invalid.yml +1 -1
- data/spec/fixtures/kontena-not-hash-service-config.yml +1 -1
- data/spec/fixtures/kontena-with-env-file.yml +2 -2
- data/spec/fixtures/kontena_build_v3.yml +23 -0
- data/spec/fixtures/kontena_v3.yml +20 -0
- data/spec/fixtures/stack-internal-extend.yml +11 -0
- data/spec/fixtures/stack-with-env-file.yml +21 -0
- data/spec/fixtures/stack-with-variables.yml +22 -0
- data/spec/kontena/cli/app/scale_spec.rb +3 -1
- data/spec/kontena/cli/cloud/login_command_spec.rb +283 -0
- data/spec/kontena/cli/master/login_command_spec.rb +324 -145
- data/spec/kontena/cli/services/link_command_spec.rb +1 -1
- data/spec/kontena/cli/services/secrets/link_command_spec.rb +4 -4
- data/spec/kontena/cli/services/secrets/unlink_command_spec.rb +2 -2
- data/spec/kontena/cli/services/services_helper_spec.rb +15 -11
- data/spec/kontena/cli/services/unlink_command_spec.rb +1 -1
- data/spec/kontena/cli/stacks/deploy_command_spec.rb +26 -0
- data/spec/kontena/cli/stacks/install_command_spec.rb +54 -0
- data/spec/kontena/cli/stacks/list_command_spec.rb +27 -0
- data/spec/kontena/cli/stacks/remove_command_spec.rb +45 -0
- data/spec/kontena/cli/stacks/service_generator_spec.rb +385 -0
- data/spec/kontena/cli/stacks/service_generator_v2_spec.rb +74 -0
- data/spec/kontena/cli/stacks/show_command_spec.rb +26 -0
- data/spec/kontena/cli/stacks/upgrade_command_spec.rb +50 -0
- data/spec/kontena/cli/stacks/yaml/reader_spec.rb +370 -0
- data/spec/kontena/cli/stacks/yaml/service_extender_spec.rb +128 -0
- data/spec/kontena/cli/stacks/yaml/validator_v3_spec.rb +302 -0
- data/spec/spec_helper.rb +6 -4
- data/spec/support/client_helpers.rb +1 -0
- metadata +57 -7
- data/lib/kontena/cli/registry/delete_command.rb +0 -18
- data/lib/kontena/cli/stacks/create_command.rb +0 -27
- data/lib/kontena/cli/stacks/update_command.rb +0 -27
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1ad6422b172a48accf054239e4dab6b61fa03f55
|
4
|
+
data.tar.gz: 536fafed2231e6fcece9120b06402aa88cab080b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 889ecc5b3aef9a47aab715c3bfc4cb1a5200589b2c8fab55ceb00c7ac5d95f2d2fb8ceb06638c5f3de870920de2102c3722f9c120d20b3c494c1c875a57942db
|
7
|
+
data.tar.gz: 2140338ef86e138a7fdf5de202b7d8246e4402ade84327a493aea66307552daeca81c38a27b7b866804ef81cc219d95236713c65ff4736b83452c9fd624f034a
|
data/.dockerignore
CHANGED
data/.gitignore
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.17.0.pre1
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Kontena
|
2
|
+
module Callbacks
|
3
|
+
class InstallSslCertificateAfterDeploy < Kontena::Callback
|
4
|
+
|
5
|
+
include Kontena::Cli::Common
|
6
|
+
|
7
|
+
matches_commands 'master create'
|
8
|
+
|
9
|
+
def after
|
10
|
+
return unless command.exit_code == 0
|
11
|
+
return unless command.result.kind_of?(Hash)
|
12
|
+
return unless command.result.has_key?(:ssl_certificate)
|
13
|
+
return unless command.result.has_key?(:public_ip)
|
14
|
+
|
15
|
+
cert_dir = File.join(Dir.home, '.kontena/certs')
|
16
|
+
unless File.directory?(cert_dir)
|
17
|
+
require 'fileutils'
|
18
|
+
FileUtils.mkdir_p(cert_dir)
|
19
|
+
end
|
20
|
+
|
21
|
+
cert_file = File.join(cert_dir, "#{command.result[:public_ip]}.pem")
|
22
|
+
|
23
|
+
spinner "Installing SSL certificate to #{cert_file}" do
|
24
|
+
File.unlink(cert_file) if File.exist?(cert_file)
|
25
|
+
File.write(cert_file, command.result[:ssl_certificate])
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
|
@@ -51,9 +51,9 @@ module Kontena::Cli::Apps
|
|
51
51
|
warning " --force-deploy will deprecate in the future, use --force"
|
52
52
|
end
|
53
53
|
spinner "Deploying #{unprefixed_name(name).colorize(:cyan)} " do
|
54
|
-
deploy_service(token, name, options)
|
54
|
+
deployment = deploy_service(token, name, options)
|
55
55
|
unless async?
|
56
|
-
wait_for_deploy_to_finish(token,
|
56
|
+
wait_for_deploy_to_finish(token, deployment)
|
57
57
|
end
|
58
58
|
end
|
59
59
|
end
|
@@ -21,8 +21,8 @@ module Kontena::Cli::Apps
|
|
21
21
|
options = yml_service[service]
|
22
22
|
exit_with_error("Service has already instances defined in #{filename}. Please update #{filename} and deploy service instead") if options['container_count']
|
23
23
|
spinner "Scaling #{service.colorize(:cyan)} " do
|
24
|
-
scale_service(require_token, prefixed_name(service), instances)
|
25
|
-
wait_for_deploy_to_finish(token,
|
24
|
+
deployment = scale_service(require_token, prefixed_name(service), instances)
|
25
|
+
wait_for_deploy_to_finish(token, deployment)
|
26
26
|
end
|
27
27
|
|
28
28
|
else
|
@@ -15,8 +15,9 @@ module Kontena::Cli::Apps
|
|
15
15
|
|
16
16
|
def execute
|
17
17
|
require_config_file(filename)
|
18
|
-
|
19
|
-
show_service(
|
18
|
+
token = require_token
|
19
|
+
show_service(token, prefixed_name(service))
|
20
|
+
show_service_instances(token, prefixed_name(service))
|
20
21
|
end
|
21
22
|
end
|
22
23
|
end
|
@@ -6,12 +6,16 @@ module Kontena::Cli::Apps::YAML
|
|
6
6
|
require_relative 'custom_validators/extends_validator'
|
7
7
|
require_relative 'custom_validators/hooks_validator'
|
8
8
|
require_relative 'custom_validators/secrets_validator'
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
9
|
+
|
10
|
+
def self.load
|
11
|
+
return if @loaded
|
12
|
+
HashValidator.append_validator(AffinitiesValidator.new)
|
13
|
+
HashValidator.append_validator(BuildValidator.new)
|
14
|
+
HashValidator.append_validator(ExtendsValidator.new)
|
15
|
+
HashValidator.append_validator(SecretsValidator.new)
|
16
|
+
HashValidator.append_validator(HooksValidator.new)
|
17
|
+
@loaded = true
|
18
|
+
end
|
15
19
|
end
|
16
20
|
|
17
21
|
def common_validations
|
@@ -4,55 +4,56 @@ module Kontena::Cli::Cloud
|
|
4
4
|
class LoginCommand < Kontena::Command
|
5
5
|
include Kontena::Cli::Common
|
6
6
|
|
7
|
-
option ['-t', '--token'], '[TOKEN]', 'Use a pre-generated access token'
|
7
|
+
option ['-t', '--token'], '[TOKEN]', 'Use a pre-generated access token', environment_variable: 'KONTENA_ACCOUNT_TOKEN'
|
8
8
|
option ['-c', '--code'], '[CODE]', 'Use an authorization code'
|
9
9
|
option ['-v', '--verbose'], :flag, 'Increase output verbosity'
|
10
|
+
option ['-f', '--force'], :flag, 'Force reauthentication'
|
10
11
|
|
11
12
|
def execute
|
12
|
-
|
13
|
-
|
13
|
+
if self.code && self.force?
|
14
|
+
exit_with_error "Can't use --code and --force together"
|
15
|
+
end
|
14
16
|
|
15
|
-
if self.
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
raise Kontena::Errors::StandardError.new(400, 'Code exchange failed') unless response
|
23
|
-
end
|
24
|
-
end
|
25
|
-
if response && response.kind_of?(Hash) && response['access_token']
|
26
|
-
kontena_account.token.access_token = response['access_token']
|
27
|
-
kontena_account.token.refresh_token = response['refresh_token']
|
28
|
-
kontena_account.token.expires_at = response['expires_in'].to_i > 0 ? Time.now.utc.to_i + response['expires_in'].to_i : nil
|
29
|
-
logger.debug "Code exchanged succesfully"
|
30
|
-
else
|
31
|
-
puts "Code exchange failed".colorize(:red)
|
32
|
-
exit 1
|
33
|
-
end
|
34
|
-
elsif self.token.nil?
|
35
|
-
token = kontena_account.token ||= Kontena::Cli::Config::Token.new(parent_type: :account, parent_name: ENV['KONTENA_ACCOUNT'] || 'kontena')
|
36
|
-
elsif self.token
|
37
|
-
kontena_account.token = Kontena::Cli::Config::Token.new(access_token: self.token, parent_type: :account, parent_name: ENV['KONTENA_ACCOUNT'] || 'kontena')
|
17
|
+
if self.token
|
18
|
+
exit_with_error "Can't use --token and --force together" if self.force?
|
19
|
+
exit_with_error "Can't use --token and --code together" if self.code
|
20
|
+
end
|
21
|
+
|
22
|
+
if !kontena_account.token || !kontena_account.token.access_token || self.token || self.force?
|
23
|
+
kontena_account.token = Kontena::Cli::Config::Token.new(access_token: self.token, parent_type: :account, parent_name: kontena_account.name)
|
38
24
|
end
|
39
25
|
|
26
|
+
use_authorization_code(self.code) if self.code
|
27
|
+
|
40
28
|
client = Kontena::Client.new(kontena_account.userinfo_endpoint, kontena_account.token, prefix: '')
|
41
29
|
|
42
30
|
if kontena_account.token.access_token
|
43
|
-
auth_ok =
|
44
|
-
|
45
|
-
auth_ok = client.authentication_ok?(kontena_account.userinfo_endpoint)
|
31
|
+
auth_ok = vspinner "Verifying current access token" do
|
32
|
+
client.authentication_ok?(kontena_account.userinfo_endpoint)
|
46
33
|
end
|
47
|
-
|
48
34
|
if auth_ok
|
49
|
-
|
50
|
-
display_logo
|
51
|
-
display_login_info(only: :account)
|
52
|
-
exit 0
|
35
|
+
finish and return
|
53
36
|
end
|
54
37
|
end
|
55
38
|
|
39
|
+
web_flow
|
40
|
+
finish
|
41
|
+
end
|
42
|
+
|
43
|
+
def finish
|
44
|
+
update_userinfo unless kontena_account.username
|
45
|
+
config.current_account = kontena_account.name
|
46
|
+
config.write
|
47
|
+
config.reset_instance
|
48
|
+
reset_cloud_client
|
49
|
+
display_logo
|
50
|
+
display_login_info(only: :account)
|
51
|
+
end
|
52
|
+
|
53
|
+
def web_flow
|
54
|
+
require_relative '../localhost_web_server'
|
55
|
+
require 'launchy'
|
56
|
+
|
56
57
|
uri = URI.parse(kontena_account.authorization_endpoint)
|
57
58
|
uri.host ||= kontena_account.url
|
58
59
|
|
@@ -85,47 +86,48 @@ module Kontena::Cli::Cloud
|
|
85
86
|
server_thread = Thread.new { Thread.main['response'] = web_server.serve_one }
|
86
87
|
browser_thread = Thread.new { Launchy.open(uri.to_s) }
|
87
88
|
|
88
|
-
|
89
|
+
spinner "Waiting for browser authorization response" do
|
89
90
|
server_thread.join
|
90
91
|
end
|
91
92
|
browser_thread.join
|
92
93
|
|
93
|
-
|
94
|
-
|
95
|
-
# If the master responds with a code, then exchange it to a token
|
96
|
-
if response && response.kind_of?(Hash) && response['code']
|
97
|
-
logger.debug 'Account responded with code, exchanging to token'
|
98
|
-
response = client.exchange_code(response['code'])
|
99
|
-
end
|
100
|
-
|
101
|
-
if response && response.kind_of?(Hash) && response['access_token']
|
102
|
-
kontena_account.token = Kontena::Cli::Config::Token.new
|
103
|
-
kontena_account.token.access_token = response['access_token']
|
104
|
-
kontena_account.token.refresh_token = response['refresh_token']
|
105
|
-
kontena_account.token.expires_at = response['expires_in'].to_i > 0 ? Time.now.utc.to_i + response['expires_in'].to_i : nil
|
106
|
-
else
|
107
|
-
puts "Authentication failed".colorize(:red)
|
108
|
-
exit 1
|
109
|
-
end
|
94
|
+
update_token(Thread.main['response'])
|
95
|
+
end
|
110
96
|
|
97
|
+
def update_userinfo
|
111
98
|
uri = URI.parse(kontena_account.userinfo_endpoint)
|
112
99
|
path = uri.path
|
113
100
|
uri.path = '/'
|
114
101
|
|
115
|
-
|
116
|
-
|
117
|
-
response = client.get(path) rescue nil
|
118
|
-
if response && response.kind_of?(Hash)
|
102
|
+
response = Kontena::Client.new(uri.to_s, kontena_account.token).get(path)
|
103
|
+
if response.kind_of?(Hash) && response['data'] && response['data']['attributes']
|
119
104
|
kontena_account.username = response['data']['attributes']['username']
|
120
|
-
|
121
|
-
|
122
|
-
display_login_info(only: :account)
|
123
|
-
config.reset_instance
|
124
|
-
reset_cloud_client
|
125
|
-
exit 0
|
105
|
+
elsif response && response['error']
|
106
|
+
exit_with_error response['error']
|
126
107
|
else
|
127
|
-
|
128
|
-
|
108
|
+
exit_with_error "Userinfo request failed"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def use_authorization_code(code)
|
113
|
+
response = vspinner "Exchanging authorization code to access token" do
|
114
|
+
Kontena::Client.new(kontena_account.token_endpoint, kontena_account.token).exchange_code(code)
|
115
|
+
end
|
116
|
+
update_token(response)
|
117
|
+
end
|
118
|
+
|
119
|
+
def update_token(response)
|
120
|
+
if !response.kind_of?(Hash)
|
121
|
+
raise TypeError, "Invalid authentication response, expected Hash, got #{response.class}"
|
122
|
+
elsif response['error']
|
123
|
+
exit_with_error "Authentication failed: #{response['error']}"
|
124
|
+
elsif response['code']
|
125
|
+
use_authorization_code(response['code'])
|
126
|
+
else
|
127
|
+
kontena_account.token.access_token = response['access_token']
|
128
|
+
kontena_account.token.refresh_token = response['refresh_token']
|
129
|
+
kontena_account.token.expires_at = response['expires_at']
|
130
|
+
true
|
129
131
|
end
|
130
132
|
end
|
131
133
|
end
|
data/lib/kontena/cli/common.rb
CHANGED
@@ -197,16 +197,6 @@ module Kontena
|
|
197
197
|
config.require_current_master.url
|
198
198
|
end
|
199
199
|
|
200
|
-
def ensure_custom_ssl_ca(url)
|
201
|
-
return if Excon.defaults[:ssl_ca_file]
|
202
|
-
|
203
|
-
uri = URI::parse(url)
|
204
|
-
cert_file = File.join(Dir.home, "/.kontena/certs/#{uri.host}.pem")
|
205
|
-
if File.exist?(cert_file)
|
206
|
-
Excon.defaults[:ssl_ca_file] = cert_file
|
207
|
-
end
|
208
|
-
end
|
209
|
-
|
210
200
|
def current_grid=(grid)
|
211
201
|
config.current_grid=(grid)
|
212
202
|
end
|
@@ -4,7 +4,6 @@ module Kontena::Cli::Grids
|
|
4
4
|
class LogsCommand < Kontena::Command
|
5
5
|
include Kontena::Cli::Common
|
6
6
|
include Kontena::Cli::Helpers::LogHelper
|
7
|
-
|
8
7
|
option "--node", "NODE", "Filter by node name", multivalued: true
|
9
8
|
option "--service", "SERVICE", "Filter by service name", multivalued: true
|
10
9
|
option ["-c", "--container"], "CONTAINER", "Filter by container", multivalued: true
|
@@ -82,9 +82,17 @@ module Kontena
|
|
82
82
|
server.close
|
83
83
|
uri = URI.parse("http://localhost#{get_request}")
|
84
84
|
ENV["DEBUG"] && puts(" * Parsing params: \"#{uri.query}\"")
|
85
|
-
params =
|
86
|
-
|
87
|
-
|
85
|
+
params = {}
|
86
|
+
URI.decode_www_form(uri.query).each do |key, value|
|
87
|
+
if value.to_s == ''
|
88
|
+
next
|
89
|
+
elsif value.to_s =~ /\A\d+\z/
|
90
|
+
params[key] = value.to_i
|
91
|
+
else
|
92
|
+
params[key] = value
|
93
|
+
end
|
94
|
+
end
|
95
|
+
params
|
88
96
|
else
|
89
97
|
# Unless it's a query to /cb, send an error message and keep listening,
|
90
98
|
# it might have been something funny like fetching favicon.ico
|
@@ -1,5 +1,3 @@
|
|
1
|
-
#TODO: Something wrong with picking up wrong server in config using the url,
|
2
|
-
#maybe remove that part altogether.
|
3
1
|
require 'uri'
|
4
2
|
|
5
3
|
module Kontena::Cli::Master
|
@@ -8,196 +6,165 @@ module Kontena::Cli::Master
|
|
8
6
|
|
9
7
|
parameter "[URL]", "Kontena Master URL or name"
|
10
8
|
option ['-j', '--join'], '[INVITE_CODE]', "Join master using an invitation code"
|
11
|
-
option ['-t', '--token'], '[TOKEN]', 'Use a pre-generated access token'
|
12
|
-
option ['-n', '--name'], '[NAME]', 'Set server name'
|
9
|
+
option ['-t', '--token'], '[TOKEN]', 'Use a pre-generated access token', environment_variable: 'KONTENA_TOKEN'
|
10
|
+
option ['-n', '--name'], '[NAME]', 'Set server name', environment_variable: 'KONTENA_MASTER'
|
13
11
|
option ['-c', '--code'], '[CODE]', 'Use authorization code generated during master install'
|
14
12
|
option ['-r', '--remote'], :flag, 'Do not try to open a browser'
|
15
13
|
option ['-e', '--expires-in'], '[SECONDS]', 'Request token with expiration of X seconds. Use 0 to never expire', default: 7200
|
16
14
|
option ['-v', '--verbose'], :flag, 'Increase output verbosity'
|
17
15
|
option ['-f', '--force'], :flag, 'Force reauthentication'
|
18
16
|
option ['-s', '--silent'], :flag, 'Reduce output verbosity'
|
17
|
+
option ['--grid'], '[GRID]', 'Set grid'
|
19
18
|
|
20
19
|
option ['--no-login-info'], :flag, "Don't show login info", hidden: true
|
21
20
|
|
22
21
|
def execute
|
23
|
-
|
24
|
-
|
22
|
+
if self.code
|
23
|
+
exit_with_error "Can't use --token and --code together" if self.token
|
24
|
+
exit_with_error "Can't use --join and --code together" if self.join
|
25
|
+
end
|
25
26
|
|
26
|
-
|
27
|
-
|
27
|
+
if self.force?
|
28
|
+
exit_with_error "Can't use --code and --force together" if self.code
|
29
|
+
exit_with_error "Can't use --token and --force together" if self.token
|
30
|
+
end
|
28
31
|
|
29
|
-
|
30
|
-
set_server_token(server)
|
32
|
+
server = select_a_server(self.name, self.url)
|
31
33
|
|
32
|
-
|
33
|
-
|
34
|
+
if self.token
|
35
|
+
# If a --token was given create a token with access_token set to --token value
|
36
|
+
server.token = Kontena::Cli::Config::Token.new(access_token: self.token, parent_type: :master, parent_name: server.name)
|
37
|
+
elsif server.token.nil? || self.force?
|
38
|
+
# Force reauth or no existing token, create a token with no access_token
|
39
|
+
server.token = Kontena::Cli::Config::Token.new(parent_type: :master, parent_name: server.name)
|
40
|
+
end
|
41
|
+
|
42
|
+
if self.grid
|
43
|
+
self.skip_grid_auto_select = true if self.respond_to?(:skip_grid_auto_select?)
|
44
|
+
server.grid = self.grid
|
45
|
+
end
|
34
46
|
|
35
|
-
|
47
|
+
# set server token by exchanging code if --code given
|
48
|
+
if self.code
|
49
|
+
use_authorization_code(server, self.code)
|
50
|
+
exit 0
|
51
|
+
end
|
36
52
|
|
37
|
-
#
|
38
|
-
# if
|
53
|
+
# unless an invitation code was supplied, check auth and exit
|
54
|
+
# if existing auth works already.
|
39
55
|
unless self.join || self.force?
|
40
56
|
if auth_works?(server)
|
41
|
-
|
42
|
-
config.reset_instance
|
57
|
+
update_server_to_config(server)
|
43
58
|
display_login_info(only: :master) unless self.no_login_info?
|
44
59
|
exit 0
|
45
60
|
end
|
46
61
|
end
|
47
62
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
end
|
54
|
-
|
55
|
-
# local web flow
|
56
|
-
response = response_from_web_flow
|
57
|
-
|
58
|
-
# If the master responds with a code, then exchange it to a token
|
59
|
-
if response['code']
|
60
|
-
use_authorization_code(server, response['code'])
|
61
|
-
elsif response['access_token']
|
62
|
-
update_server_token(server, response)
|
63
|
-
update_server_name(server, response)
|
64
|
-
config.current_server = server.name
|
65
|
-
end
|
66
|
-
config.write
|
67
|
-
display_login_info(only: :master) unless (running_silent? || self.no_login_info?)
|
68
|
-
end
|
69
|
-
|
70
|
-
def master_account
|
71
|
-
@master_account ||= config.find_account('master')
|
72
|
-
end
|
63
|
+
auth_params = {
|
64
|
+
remote: self.remote?,
|
65
|
+
invite_code: self.join,
|
66
|
+
expires_in: self.expires_in
|
67
|
+
}
|
73
68
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
69
|
+
if self.remote?
|
70
|
+
# no local browser? tell user to launch an external one
|
71
|
+
display_remote_message(server, auth_params)
|
72
|
+
update_server_to_config(server)
|
73
|
+
exit 1
|
79
74
|
else
|
80
|
-
|
75
|
+
# local web flow
|
76
|
+
web_flow(server, auth_params)
|
77
|
+
display_login_info(only: :master) unless (running_silent? || self.no_login_info?)
|
81
78
|
end
|
82
79
|
end
|
83
80
|
|
84
|
-
def
|
85
|
-
|
86
|
-
server = config.find_server(self.url)
|
87
|
-
if server && server.url
|
88
|
-
self.url = server.url
|
89
|
-
true
|
90
|
-
else
|
91
|
-
exit_with_error "Server '#{self.url}' not found in configuration."
|
92
|
-
end
|
81
|
+
def next_default_name
|
82
|
+
next_name('kontena-master')
|
93
83
|
end
|
94
84
|
|
95
|
-
def
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
85
|
+
def next_name(base)
|
86
|
+
if config.find_server(base)
|
87
|
+
new_name = base.dup
|
88
|
+
unless new_name =~ /\-\d+$/
|
89
|
+
new_name += "-2"
|
90
|
+
end
|
91
|
+
new_name.succ! until config.find_server(new_name).nil?
|
92
|
+
new_name
|
100
93
|
else
|
101
|
-
|
102
|
-
config.servers << new_server
|
103
|
-
config.current_server = new_server.name
|
104
|
-
new_server
|
94
|
+
base
|
105
95
|
end
|
106
96
|
end
|
107
97
|
|
108
|
-
def
|
109
|
-
|
110
|
-
# Use supplied token
|
111
|
-
server.token = Kontena::Cli::Config::Token.new(access_token: self.token, parent_type: :master, parent_name: server.name)
|
112
|
-
elsif server.token.nil? || self.force?
|
113
|
-
# Create new empty token if the server does not have one yet
|
114
|
-
server.token = Kontena::Cli::Config::Token.new(parent_type: :master, parent_name: server.name)
|
115
|
-
end
|
98
|
+
def master_account
|
99
|
+
@master_account ||= config.find_account('master')
|
116
100
|
end
|
117
101
|
|
118
102
|
def use_authorization_code(server, code)
|
119
|
-
vspinner "Exchanging authorization code for an access token from Kontena Master" do
|
120
|
-
|
121
|
-
begin
|
122
|
-
response = client.exchange_code(code)
|
123
|
-
rescue StandardError => ex
|
124
|
-
ENV["DEBUG"] && puts("#{ex}\n#{ex.backtrace.join(" \n")}")
|
125
|
-
exit_with_error "Code exchange failed: #{ex}"
|
126
|
-
end
|
127
|
-
|
128
|
-
if response['server'] && response['server']['name']
|
129
|
-
server.name ||= response['server']['name']
|
130
|
-
end
|
131
|
-
|
132
|
-
if response['user']
|
133
|
-
server.username = response['user']['name'] || response['user']['email']
|
134
|
-
end
|
135
|
-
|
136
|
-
server.token = Kontena::Cli::Config::Token.new(
|
137
|
-
access_token: response['access_token'],
|
138
|
-
refresh_token: response['refresh_token'],
|
139
|
-
expires_at: in_to_at(response['expires_in']),
|
140
|
-
)
|
141
|
-
|
142
|
-
config.current_server = server.name
|
103
|
+
response = vspinner "Exchanging authorization code for an access token from Kontena Master" do
|
104
|
+
Kontena::Client.new(server.url, server.token).exchange_code(code)
|
143
105
|
end
|
144
|
-
|
106
|
+
update_server(server, response)
|
107
|
+
update_server_to_config(server)
|
145
108
|
end
|
146
109
|
|
110
|
+
# Check if the existing (or --token) authentication works without reauthenticating
|
147
111
|
def auth_works?(server)
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
vspinner "Testing if authentication works using current access token" do
|
152
|
-
auth_ok = Kontena::Client.new(server.url, server.token).authentication_ok?(master_account.userinfo_endpoint)
|
153
|
-
config.current_master = server.name
|
154
|
-
end
|
155
|
-
auth_ok
|
156
|
-
else
|
157
|
-
false
|
112
|
+
return false unless (server && server.token && server.token.access_token)
|
113
|
+
vspinner "Testing if authentication works using current access token" do
|
114
|
+
Kontena::Client.new(server.url, server.token).authentication_ok?(master_account.userinfo_endpoint)
|
158
115
|
end
|
159
116
|
end
|
160
117
|
|
161
|
-
|
118
|
+
# Build a path for master authentication
|
119
|
+
#
|
120
|
+
# @param local_port [Fixnum] tcp port where localhost webserver is listening
|
121
|
+
# @param invite_code [String] an invitation code generated when user was invited
|
122
|
+
# @param expires_in [Fixnum] expiration time for the requested access token
|
123
|
+
# @param remote [Boolean] true when performing a login where the code is displayed on the web page
|
124
|
+
# @return [String]
|
125
|
+
def authentication_path(local_port: nil, invite_code: nil, expires_in: nil, remote: false)
|
162
126
|
auth_url_params = {}
|
163
|
-
if
|
127
|
+
if remote
|
164
128
|
auth_url_params[:redirect_uri] = "/code"
|
129
|
+
elsif local_port
|
130
|
+
auth_url_params[:redirect_uri] = "http://localhost:#{local_port}/cb"
|
165
131
|
else
|
166
|
-
|
132
|
+
raise ArgumentError, "Local port not defined and not performing remote login"
|
167
133
|
end
|
168
|
-
auth_url_params[:invite_code] =
|
169
|
-
auth_url_params[:expires_in] =
|
134
|
+
auth_url_params[:invite_code] = invite_code if invite_code
|
135
|
+
auth_url_params[:expires_in] = expires_in if expires_in
|
170
136
|
"/authenticate?#{URI.encode_www_form(auth_url_params)}"
|
171
137
|
end
|
172
138
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
139
|
+
# Request a redirect to the authentication url from master
|
140
|
+
#
|
141
|
+
# @param master_url [String] master root url
|
142
|
+
# @param auth_params [Hash] auth parameters (keyword arguments of #authentication_path)
|
143
|
+
# @return [String] url to begin authentication web flow
|
144
|
+
def authentication_url_from_master(master_url, auth_params)
|
145
|
+
client = Kontena::Client.new(master_url)
|
178
146
|
vspinner "Sending authentication request to receive an authorization URL" do
|
179
|
-
|
147
|
+
response = client.request(
|
180
148
|
http_method: :get,
|
181
|
-
path:
|
149
|
+
path: authentication_path(auth_params),
|
182
150
|
expects: [501, 400, 302, 403],
|
183
151
|
auth: false
|
184
152
|
)
|
185
153
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
exit_with_error "Invalid invitation code"
|
154
|
+
if client.last_response.status == 302
|
155
|
+
client.last_response.headers['Location']
|
156
|
+
elsif response.kind_of?(Hash)
|
157
|
+
exit_with_error [response['error'], response['error_description']].compact.join(' : ')
|
158
|
+
elsif response.kind_of?(String) && response.length > 1
|
159
|
+
exit_with_error response
|
193
160
|
else
|
194
|
-
exit_with_error "Invalid response to authentication request"
|
161
|
+
exit_with_error "Invalid response to authentication request : HTTP#{client.last_response.status} #{client.last_response.body if ENV["DEBUG"]}"
|
195
162
|
end
|
196
163
|
end
|
197
|
-
authorization_url
|
198
164
|
end
|
199
165
|
|
200
|
-
def
|
166
|
+
def display_remote_message(server, auth_params)
|
167
|
+
url = authentication_url_from_master(server.url, auth_params.merge(remote: true))
|
201
168
|
if running_silent?
|
202
169
|
sputs url
|
203
170
|
else
|
@@ -205,21 +172,21 @@ module Kontena::Cli::Master
|
|
205
172
|
puts "#{url}"
|
206
173
|
puts
|
207
174
|
puts "Then complete the authentication by using:"
|
208
|
-
puts "kontena master login --code <CODE FROM BROWSER>"
|
209
|
-
# Using exit code 1 because the operation isn't complete,
|
210
|
-
# you can't do something like:
|
211
|
-
# kontena master login --remote && echo "yes"
|
175
|
+
puts "kontena master login --code <CODE FROM BROWSER> #{server.url}"
|
212
176
|
end
|
213
|
-
exit 1
|
214
177
|
end
|
215
178
|
|
216
|
-
def
|
179
|
+
def web_flow(server, auth_params)
|
217
180
|
require_relative '../localhost_web_server'
|
218
181
|
require 'launchy'
|
219
182
|
|
183
|
+
|
220
184
|
web_server = Kontena::LocalhostWebServer.new
|
221
|
-
|
222
|
-
|
185
|
+
|
186
|
+
url = authentication_url_from_master(server.url, auth_params.merge(local_port: web_server.port))
|
187
|
+
uri = URI.parse(url)
|
188
|
+
|
189
|
+
puts "Opening a browser to #{uri.scheme}://#{uri.host}"
|
223
190
|
puts
|
224
191
|
puts "If you are running this command over an ssh connection or it's"
|
225
192
|
puts "otherwise not possible to open a browser from this terminal"
|
@@ -239,44 +206,127 @@ module Kontena::Cli::Master
|
|
239
206
|
server_thread = Thread.new { Thread.main['response'] = web_server.serve_one }
|
240
207
|
browser_thread = Thread.new { Launchy.open(uri.to_s) }
|
241
208
|
|
242
|
-
|
209
|
+
spinner "Waiting for browser authorization response" do
|
243
210
|
server_thread.join
|
244
211
|
end
|
245
212
|
browser_thread.join
|
246
213
|
|
247
|
-
Thread.main['response']
|
214
|
+
update_server(server, Thread.main['response'])
|
215
|
+
update_server_to_config(server)
|
248
216
|
end
|
249
217
|
|
250
|
-
def
|
251
|
-
|
252
|
-
|
218
|
+
def update_server(server, response)
|
219
|
+
update_server_token(server, response)
|
220
|
+
update_server_name(server, response)
|
221
|
+
update_server_username(server, response)
|
222
|
+
end
|
223
|
+
|
224
|
+
def update_server_name(server, response)
|
225
|
+
return nil unless server.name.nil?
|
226
|
+
if response.kind_of?(Hash) && response['server'] && response['server']['name']
|
227
|
+
server.name = next_name(response['server']['name'])
|
253
228
|
else
|
254
|
-
|
229
|
+
server.name = next_default_name
|
255
230
|
end
|
256
231
|
end
|
257
232
|
|
258
|
-
def
|
259
|
-
|
260
|
-
|
261
|
-
server.token.
|
262
|
-
server.token.expires_at = in_to_at(response['expires_in'])
|
263
|
-
server.token.username = response.fetch('user', {}).fetch('name', nil) || response.fetch('user', {}).fetch('email', nil)
|
233
|
+
def update_server_username(server, response)
|
234
|
+
return nil unless response.kind_of?(Hash)
|
235
|
+
return nil unless response['user']
|
236
|
+
server.token.username = response['user']['name'] || response['user']['email']
|
264
237
|
server.username = server.token.username
|
265
238
|
end
|
266
239
|
|
267
|
-
def
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
server
|
272
|
-
elsif response['
|
273
|
-
|
274
|
-
elsif config.find_server('kontena-master')
|
275
|
-
new_name = "kontena-master-2"
|
276
|
-
new_name.succ! until config.find_server(new_name).nil?
|
277
|
-
server.name = new_name
|
240
|
+
def update_server_token(server, response)
|
241
|
+
if !response.kind_of?(Hash)
|
242
|
+
raise TypeError, "Response type mismatch - expected Hash, got #{response.class}"
|
243
|
+
elsif response['code']
|
244
|
+
use_authorization_code(server, response['code'])
|
245
|
+
elsif response['error']
|
246
|
+
exit_with_error "Authentication failed: #{response['error']} #{response['error_description']}"
|
278
247
|
else
|
279
|
-
server.
|
248
|
+
server.token = Kontena::Cli::Config::Token.new
|
249
|
+
server.token.access_token = response['access_token']
|
250
|
+
server.token.refresh_token = response['refresh_token']
|
251
|
+
server.token.expires_at = response['expires_at']
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def update_server_to_config(server)
|
256
|
+
server.name ||= next_default_name
|
257
|
+
config.servers << server unless config.servers.include?(server)
|
258
|
+
config.current_server = server.name
|
259
|
+
config.write
|
260
|
+
config.reset_instance
|
261
|
+
end
|
262
|
+
|
263
|
+
# Figure out or create a server based on url or name.
|
264
|
+
#
|
265
|
+
# No name or url provided: try to use current_master
|
266
|
+
# A name provided with --name but no url defined: try to find a server by name from config
|
267
|
+
# An URL starting with 'http' provided: try to find a server by url from config
|
268
|
+
# An URL not starting with 'http' provided: try to find a server by name
|
269
|
+
# An URL and a name provided
|
270
|
+
# - If a server is found by name: use entry and update URL to the provided url
|
271
|
+
# - Else create a new entry with the url and name
|
272
|
+
#
|
273
|
+
# @param name [String] master name
|
274
|
+
# @param url [String] master url or name
|
275
|
+
# @return [Kontena::Cli::Config::Server]
|
276
|
+
def select_a_server(name, url)
|
277
|
+
# no url, no name, try to use current master
|
278
|
+
if url.nil? && name.nil?
|
279
|
+
if config.current_master
|
280
|
+
return config.current_master
|
281
|
+
else
|
282
|
+
exit_with_error 'URL not specified and current master not selected'
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
if name && url
|
287
|
+
exact_match = config.find_server_by(url: url, name: name)
|
288
|
+
return exact_match if exact_match # found an exact match, going to use that one.
|
289
|
+
|
290
|
+
name_match = config.find_server(name)
|
291
|
+
|
292
|
+
if name_match
|
293
|
+
#found a server with the provided name, set the provided url to it and return
|
294
|
+
name_match.url = url
|
295
|
+
return name_match
|
296
|
+
else
|
297
|
+
# nothing found, create new.
|
298
|
+
return Kontena::Cli::Config::Server.new(name: name, url: url)
|
299
|
+
end
|
300
|
+
elsif name
|
301
|
+
# only --name provided, try to find a server with that name
|
302
|
+
name_match = config.find_server(name)
|
303
|
+
|
304
|
+
if name_match && name_match.url
|
305
|
+
return name_match
|
306
|
+
else
|
307
|
+
exit_with_error "Master #{name} was found from config, but it does not have an URL and no URL was provided on command line"
|
308
|
+
end
|
309
|
+
elsif url
|
310
|
+
# only url provided
|
311
|
+
if url =~ /^https?:\/\//
|
312
|
+
# url is actually an url
|
313
|
+
url_match = config.find_server_by(url: url)
|
314
|
+
if url_match
|
315
|
+
return url_match
|
316
|
+
else
|
317
|
+
return Kontena::Cli::Config::Server.new(url: url, name: nil)
|
318
|
+
end
|
319
|
+
else
|
320
|
+
name_match = config.find_server(url)
|
321
|
+
if name_match
|
322
|
+
unless name_match.url
|
323
|
+
exit_with_error "Master #{url} was found from config, but it does not have an URL and no URL was provided on command line"
|
324
|
+
end
|
325
|
+
return name_match
|
326
|
+
else
|
327
|
+
exit_with_error "Can't find a master with name #{name} from configuration"
|
328
|
+
end
|
329
|
+
end
|
280
330
|
end
|
281
331
|
end
|
282
332
|
|