knife-zero 1.19.6 → 2.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -7
- data/README.md +2 -3
- data/knife-zero.gemspec +21 -21
- data/lib/chef/knife/zero_apply.rb +20 -21
- data/lib/chef/knife/zero_base.rb +9 -10
- data/lib/chef/knife/zero_bootstrap.rb +50 -73
- data/lib/chef/knife/zero_chef_client.rb +1 -1
- data/lib/chef/knife/zero_converge.rb +41 -41
- data/lib/chef/knife/zero_diagnose.rb +14 -14
- data/lib/knife-zero/bootstrap_ssh.rb +11 -14
- data/lib/knife-zero/core/bootstrap_context.rb +65 -65
- data/lib/knife-zero/devpatch/train_connector.rb +26 -0
- data/lib/knife-zero/helper.rb +6 -5
- data/lib/knife-zero/{net-ssh-multi-patch.rb → net_ssh_multi_patch.rb} +5 -1
- data/lib/knife-zero/version.rb +1 -1
- metadata +15 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7e95f3c1de4d14fab19d32c3b3654efeaa17565815efe0a54c21db5db118e1e6
|
4
|
+
data.tar.gz: a84cc6bbc4ec236c8d043d4ebeb95f4cab208d3b0b42108c51f58683a38eb674
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 895dfe7c70f06142c67300659e7ae563494833c68aad69f2f18dc5021967958fc3bbca1834e95206827a28b250a7aa0bf604e95cf98793d71c24a23ff9e20d5b
|
7
|
+
data.tar.gz: 5aeaf0628f3878a490dfb624223673f44b16edabcfb0c4b8e9caee5ace7b99090fa920ddb5e4877948c3aa777ad83c0eaa8a3f8f928f7a1f1c30a157acda8cd1
|
data/CHANGELOG.md
CHANGED
@@ -2,15 +2,12 @@
|
|
2
2
|
|
3
3
|
## Unreleased
|
4
4
|
|
5
|
-
## v1.19.6
|
6
5
|
|
7
|
-
|
6
|
+
## v2.0.0
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
- set `chef < 15.0` for 1.x
|
8
|
+
- Support bootstrap changes on Chef Infra Client 15.x
|
9
|
+
- without Windows
|
10
|
+
- drop support chef-client < 15
|
14
11
|
|
15
12
|
## v1.19.3
|
16
13
|
|
data/README.md
CHANGED
@@ -6,8 +6,6 @@
|
|
6
6
|
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/higanworks/knife-zero?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
7
7
|
|
8
8
|
[![Gem Version](https://badge.fury.io/rb/knife-zero.svg)](http://badge.fury.io/rb/knife-zero)
|
9
|
-
[![Stories in Ready](https://badge.waffle.io/higanworks/knife-zero.svg?label=ready&title=Ready)](http://waffle.io/higanworks/knife-zero)
|
10
|
-
[![Stories in Progress](https://badge.waffle.io/higanworks/knife-zero.svg?label=In%20Progress&title=In%20Progress)](http://waffle.io/higanworks/knife-zero)
|
11
9
|
|
12
10
|
Run chef-client at remote node with chef-zero(local-mode) via HTTP over SSH port forwarding.
|
13
11
|
|
@@ -23,7 +21,8 @@ Run chef-client at remote node with chef-zero(local-mode) via HTTP over SSH port
|
|
23
21
|
|
24
22
|
## Requirements
|
25
23
|
|
26
|
-
-
|
24
|
+
- Ruby 2.5 or later
|
25
|
+
- Must support AllowTcpForward
|
27
26
|
|
28
27
|
## Installation
|
29
28
|
|
data/knife-zero.gemspec
CHANGED
@@ -1,32 +1,32 @@
|
|
1
|
-
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
1
|
+
lib = File.expand_path('lib', __dir__)
|
3
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
3
|
require 'knife-zero/version'
|
5
4
|
|
6
5
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name =
|
6
|
+
spec.name = 'knife-zero'
|
8
7
|
spec.version = Knife::Zero::VERSION
|
9
|
-
spec.authors = [
|
10
|
-
spec.email = [
|
11
|
-
spec.summary =
|
12
|
-
spec.description =
|
13
|
-
spec.homepage =
|
14
|
-
spec.license =
|
8
|
+
spec.authors = ['sawanoboly']
|
9
|
+
spec.email = ['sawanoboriyu@higanworks.com']
|
10
|
+
spec.summary = 'Run chef-client at remote node with chef-zero(local-mode) via HTTP over SSH port fowarding.'
|
11
|
+
spec.description = 'Run chef-client at remote node with chef-zero(local-mode) via HTTP over SSH port fowarding.'
|
12
|
+
spec.homepage = 'http://knife-zero.github.io'
|
13
|
+
spec.license = 'Apache-2.0'
|
14
|
+
spec.required_ruby_version = '>= 2.5.0'
|
15
15
|
|
16
|
-
spec.files = Dir['README.md','CHANGELOG.md','knife-zero.gemspec','lib/**/*']
|
16
|
+
spec.files = Dir['README.md', 'CHANGELOG.md', 'knife-zero.gemspec', 'lib/**/*']
|
17
17
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
-
spec.require_paths = [
|
19
|
+
spec.require_paths = ['lib']
|
20
20
|
|
21
|
-
spec.add_development_dependency
|
22
|
-
spec.add_development_dependency
|
23
|
-
spec.add_development_dependency
|
24
|
-
spec.add_development_dependency
|
25
|
-
spec.add_development_dependency
|
26
|
-
spec.add_development_dependency
|
27
|
-
spec.add_development_dependency
|
28
|
-
spec.add_development_dependency
|
29
|
-
spec.add_development_dependency
|
21
|
+
spec.add_development_dependency 'bundler', '>= 1.6'
|
22
|
+
spec.add_development_dependency 'guard'
|
23
|
+
spec.add_development_dependency 'guard-shell'
|
24
|
+
spec.add_development_dependency 'rake'
|
25
|
+
spec.add_development_dependency 'simplecov'
|
26
|
+
spec.add_development_dependency 'simplecov-rcov'
|
27
|
+
spec.add_development_dependency 'test-unit'
|
28
|
+
spec.add_development_dependency 'test-unit-notify'
|
29
|
+
spec.add_development_dependency 'test-unit-rr'
|
30
30
|
|
31
|
-
spec.add_runtime_dependency
|
31
|
+
spec.add_runtime_dependency 'chef', '>= 15.0'
|
32
32
|
end
|
@@ -13,10 +13,10 @@ class Chef
|
|
13
13
|
deps do
|
14
14
|
Chef::Knife::BootstrapSsh.load_deps
|
15
15
|
Chef::Knife::ZeroConverge.load_deps
|
16
|
-
require
|
16
|
+
require 'knife-zero/helper'
|
17
17
|
end
|
18
18
|
|
19
|
-
banner
|
19
|
+
banner 'knife zero apply QUERY (options)'
|
20
20
|
|
21
21
|
self.options = Ssh.options.merge(self.options)
|
22
22
|
self.options = ZeroConverge.options.merge(self.options)
|
@@ -24,42 +24,41 @@ class Chef
|
|
24
24
|
self.options.delete(:override_runlist)
|
25
25
|
|
26
26
|
option :recipe,
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
27
|
+
short: '-r Recipe String or @filename',
|
28
|
+
long: '--recipe Recipe String or @filename',
|
29
|
+
description: 'Recipe for execute by chef-apply',
|
30
|
+
default: '',
|
31
|
+
proc: lambda { |o| o.start_with?('@') ? File.read(o[1..-1]).shellescape : o.to_s.shellescape }
|
32
32
|
|
33
33
|
## Import from Chef-Apply
|
34
34
|
self.options[:minimal_ohai] = Chef::Application::Apply.options[:minimal_ohai]
|
35
35
|
self.options[:json_attribs] = Chef::Application::Apply.options[:json_attribs]
|
36
|
-
self.options[:json_attribs][:description] =
|
36
|
+
self.options[:json_attribs][:description] = 'Load attributes from a JSON file or URL (retrieves from the remote node)'
|
37
37
|
|
38
|
-
|
39
|
-
def initialize(argv=[])
|
38
|
+
def initialize(argv = [])
|
40
39
|
super
|
41
40
|
self.configure_chef
|
42
41
|
|
43
42
|
@name_args = [@name_args[0], start_chef_apply]
|
44
43
|
end
|
45
44
|
|
46
|
-
def start_chef_apply
|
47
|
-
if @config[:verbosity]
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
45
|
+
def start_chef_apply # rubocop:disable Metrics/PerceivedComplexity, Metrics/AbcSize, Metrics/CyclomaticComplexity
|
46
|
+
log_level = if @config[:verbosity] && @config[:verbosity] >= 2
|
47
|
+
'debug'
|
48
|
+
else
|
49
|
+
'info'
|
50
|
+
end
|
52
51
|
|
53
52
|
client_path = @config[:use_sudo] || Chef::Config[:knife][:use_sudo] ? 'sudo ' : ''
|
54
53
|
client_path = @config[:chef_client_path] ? "#{client_path}#{@config[:chef_client_path]}" : "#{client_path}chef-apply"
|
55
54
|
s = String.new("echo #{@config[:recipe]} | #{client_path}")
|
56
55
|
s << " -l #{log_level}"
|
57
|
-
s <<
|
58
|
-
s <<
|
56
|
+
s << ' -s'
|
57
|
+
s << ' --minimal-ohai' if @config[:minimal_ohai]
|
59
58
|
s << " -j #{@config[:json_attribs]}" if @config[:json_attribs]
|
60
|
-
s <<
|
61
|
-
s <<
|
62
|
-
Chef::Log.info
|
59
|
+
s << ' --no-color' unless @config[:color]
|
60
|
+
s << ' -W' if @config[:why_run]
|
61
|
+
Chef::Log.info 'Remote command: ' + s
|
63
62
|
s
|
64
63
|
end
|
65
64
|
end
|
data/lib/chef/knife/zero_base.rb
CHANGED
@@ -12,23 +12,22 @@ class Chef
|
|
12
12
|
## deprecated CHEF-18.
|
13
13
|
## TODO: should implement unix domain socket forwarding (~< net-ssh 4.1.0) before will be removed.
|
14
14
|
Chef::Config[:listen] = true
|
15
|
-
Chef::Config[:knife_zero] =
|
15
|
+
Chef::Config[:knife_zero] = {}
|
16
16
|
Chef::Knife::Ssh.load_deps
|
17
17
|
end
|
18
18
|
|
19
19
|
## Added by Knife-Zero
|
20
20
|
option :why_run,
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
21
|
+
short: '-W',
|
22
|
+
long: '--why-run',
|
23
|
+
description: 'Enable whyrun mode on chef-client run at remote node.',
|
24
|
+
boolean: true
|
25
25
|
|
26
26
|
option :remote_chef_zero_port,
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
27
|
+
long: '--remote-chef-zero-port PORT',
|
28
|
+
description: 'Listen port on remote',
|
29
|
+
default: nil,
|
30
|
+
proc: proc { |key| Chef::Config[:remote_chef_zero_port] = key.to_i }
|
32
31
|
end
|
33
32
|
end
|
34
33
|
|
@@ -7,55 +7,52 @@ class Chef
|
|
7
7
|
class ZeroBootstrap < Chef::Knife::Bootstrap
|
8
8
|
include Chef::Knife::ZeroBase
|
9
9
|
deps do
|
10
|
+
require 'erubis' unless defined?(Erubis)
|
11
|
+
|
12
|
+
require 'chef/knife/bootstrap/chef_vault_handler'
|
13
|
+
require 'chef/knife/bootstrap/client_builder'
|
14
|
+
require 'chef/knife/bootstrap/train_connector'
|
10
15
|
require 'knife-zero/core/bootstrap_context'
|
11
|
-
require 'knife-zero/
|
12
|
-
|
16
|
+
require 'knife-zero/devpatch/train_connector'
|
17
|
+
require 'knife-zero/helper'
|
13
18
|
|
14
19
|
self.options.delete :node_ssl_verify_mode
|
15
20
|
self.options.delete :node_verify_api_cert
|
16
|
-
|
17
|
-
## Override to use nil by default. It should be create PR
|
18
|
-
option :ssh_user,
|
19
|
-
:short => "-x USERNAME",
|
20
|
-
:long => "--ssh-user USERNAME"
|
21
|
-
|
22
|
-
## For support policy_document_databag(old style)
|
23
|
-
self.options[:policy_name][:description] = "Policy name to use (F.Y.I: Default policy_group=local)"
|
24
|
-
|
25
|
-
## Set `local` to default policy_group
|
26
|
-
self.options[:policy_group][:description] = "Policy group name to use (--policy-name must also be given). use 'local' "
|
27
|
-
self.options[:policy_group][:default] = "local"
|
28
21
|
end
|
29
22
|
|
30
|
-
banner
|
23
|
+
banner 'knife zero bootstrap [SSH_USER@]FQDN (options)'
|
31
24
|
|
32
25
|
## Import from knife bootstrap except exclusions
|
33
26
|
self.options = Bootstrap.options.merge(self.options)
|
34
27
|
|
35
28
|
option :bootstrap_converge,
|
36
|
-
:
|
37
|
-
:
|
38
|
-
:
|
39
|
-
:
|
40
|
-
:
|
29
|
+
long: '--[no-]converge',
|
30
|
+
description: 'Bootstrap without Chef-Client Run.(for only update client.rb)',
|
31
|
+
boolean: true,
|
32
|
+
default: true,
|
33
|
+
proc: lambda { |v| Chef::Config[:knife][:bootstrap_converge] = v }
|
41
34
|
|
42
35
|
option :overwrite_node_object,
|
43
|
-
:
|
44
|
-
:
|
45
|
-
:
|
46
|
-
:
|
47
|
-
:
|
36
|
+
long: '--[no-]overwrite',
|
37
|
+
description: 'Overwrite local node object if node already exist. false by default',
|
38
|
+
boolean: true,
|
39
|
+
default: false,
|
40
|
+
proc: lambda { |v| Chef::Config[:knife][:overwrite_node_object] = v }
|
48
41
|
|
49
42
|
option :appendix_config,
|
50
|
-
:
|
51
|
-
:
|
52
|
-
:
|
53
|
-
:
|
43
|
+
long: '--appendix-config PATH',
|
44
|
+
description: 'Append lines to end of client.rb on remote node from file.',
|
45
|
+
proc: lambda { |o| File.read(o) },
|
46
|
+
default: nil
|
54
47
|
|
55
|
-
def run
|
48
|
+
def run # rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/AbcSize, Metrics/CyclomaticComplexity
|
56
49
|
## Command hook before_bootstrap (After launched Chef-Zero)
|
57
50
|
if Chef::Config[:knife][:before_bootstrap]
|
58
|
-
::Knife::Zero::Helper.hook_shell_out!(
|
51
|
+
::Knife::Zero::Helper.hook_shell_out!(
|
52
|
+
'before_bootstrap',
|
53
|
+
ui,
|
54
|
+
Chef::Config[:knife][:before_bootstrap]
|
55
|
+
)
|
59
56
|
end
|
60
57
|
|
61
58
|
if @config[:bootstrap_converge]
|
@@ -64,81 +61,61 @@ class Chef
|
|
64
61
|
node_name = resolve_node_name
|
65
62
|
result = q.search(:node, "name:#{node_name} OR knife_zero_host:#{node_name}")
|
66
63
|
if result.last > 0
|
67
|
-
ui.warn(%
|
64
|
+
ui.warn(%{Node "#{node_name}" already exist. [Found #{result.last} Node(s) in local search.] (You can skip asking with --overwrite option.)})
|
68
65
|
if result.last == 1
|
69
|
-
ui.confirm(%
|
66
|
+
ui.confirm(%{Overwrite it }, true, false)
|
70
67
|
else
|
71
|
-
ui.confirm(%
|
68
|
+
ui.confirm(%{Overwrite one of them }, true, false)
|
72
69
|
end
|
73
70
|
end
|
74
71
|
end
|
75
72
|
end
|
76
73
|
|
77
|
-
|
78
74
|
if @config[:first_boot_attributes_from_file]
|
79
75
|
@config[:first_boot_attributes_from_file] = @config[:first_boot_attributes_from_file].merge(build_knifezero_attributes_for_node)
|
80
76
|
else
|
81
|
-
if @config[:first_boot_attributes]
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
77
|
+
@config[:first_boot_attributes] = if @config[:first_boot_attributes]
|
78
|
+
@config[:first_boot_attributes].merge(build_knifezero_attributes_for_node)
|
79
|
+
else
|
80
|
+
@config[:first_boot_attributes] = build_knifezero_attributes_for_node
|
81
|
+
end
|
86
82
|
end
|
87
|
-
super
|
88
|
-
end
|
89
83
|
|
90
|
-
|
91
|
-
|
92
|
-
ssh = Chef::Knife::BootstrapSsh.new
|
93
|
-
ssh.ui = ui
|
94
|
-
ssh.name_args = [ server_name, ssh_command ]
|
95
|
-
ssh.config = Net::SSH.configuration_for(server_name, true)
|
96
|
-
ssh.config[:ssh_user] = user_name || config[:ssh_user] || Chef::Config[:knife][:ssh_user]
|
97
|
-
ssh.config[:ssh_password] = config[:ssh_password]
|
98
|
-
ssh.config[:ssh_port] = config[:ssh_port] || Chef::Config[:knife][:ssh_port]
|
99
|
-
ssh.config[:ssh_gateway] = config[:ssh_gateway] || Chef::Config[:knife][:ssh_gateway]
|
100
|
-
ssh.config[:forward_agent] = config[:forward_agent] || Chef::Config[:knife][:forward_agent]
|
101
|
-
ssh.config[:identity_file] = config[:identity_file] || Chef::Config[:knife][:identity_file] # DEPRECATED
|
102
|
-
ssh.config[:ssh_identity_file] = config[:ssh_identity_file] || Chef::Config[:knife][:ssh_identity_file]
|
103
|
-
ssh.config[:manual] = true
|
104
|
-
ssh.config[:host_key_verify] = config[:host_key_verify] || Chef::Config[:knife][:host_key_verify]
|
105
|
-
ssh.config[:on_error] = :raise
|
106
|
-
ssh
|
107
|
-
rescue => e
|
108
|
-
ui.error(e.class.to_s + e.message)
|
109
|
-
ui.error e.backtrace.join("\n")
|
110
|
-
exit 1
|
84
|
+
File.open(client_builder.client_path, 'w') do |f|
|
85
|
+
f.puts OpenSSL::PKey::RSA.new(2048).to_s
|
111
86
|
end
|
87
|
+
super
|
112
88
|
end
|
113
89
|
|
114
90
|
## For support policy_document_databag(old style)
|
115
91
|
def validate_options!
|
116
92
|
if policyfile_and_run_list_given?
|
117
|
-
ui.error(
|
93
|
+
ui.error('Policyfile options and --run-list are exclusive')
|
118
94
|
exit 1
|
119
95
|
end
|
120
96
|
true
|
121
97
|
end
|
122
98
|
|
123
99
|
def build_knifezero_attributes_for_node
|
124
|
-
## Return to Pending.
|
125
|
-
#
|
126
|
-
#
|
127
|
-
#
|
128
|
-
#
|
129
|
-
#
|
130
|
-
#
|
131
|
-
#
|
100
|
+
## Return to Pending.
|
101
|
+
# ssh_url = String.new("ssh://")
|
102
|
+
# ssh_url << config[:ssh_user] || Chef::Config[:knife][:ssh_user]
|
103
|
+
# ssh_url << "@"
|
104
|
+
# ssh_url << server_name
|
105
|
+
# ssh_url << ":"
|
106
|
+
# port = config[:ssh_port] || Chef::Config[:knife][:ssh_port] || 22
|
107
|
+
# ssh_url << port.to_s
|
132
108
|
attr = Mash.new
|
133
109
|
attr[:knife_zero] = {
|
134
110
|
host: server_name
|
135
|
-
#
|
111
|
+
# ssh_url: ssh_url
|
136
112
|
}
|
137
113
|
attr
|
138
114
|
end
|
139
115
|
|
140
116
|
def resolve_node_name
|
141
117
|
return @config[:chef_node_name] if @config[:chef_node_name]
|
118
|
+
|
142
119
|
@cli_arguments.first.split('@').last
|
143
120
|
end
|
144
121
|
end
|
@@ -12,10 +12,10 @@ class Chef
|
|
12
12
|
deps do
|
13
13
|
require 'chef/run_list/run_list_item'
|
14
14
|
Chef::Knife::BootstrapSsh.load_deps
|
15
|
-
require
|
15
|
+
require 'knife-zero/helper'
|
16
16
|
end
|
17
17
|
|
18
|
-
banner
|
18
|
+
banner 'knife zero converge QUERY (options)'
|
19
19
|
|
20
20
|
self.options = Ssh.options.merge(self.options)
|
21
21
|
self.options[:use_sudo_password] = Bootstrap.options[:use_sudo_password]
|
@@ -37,50 +37,49 @@ class Chef
|
|
37
37
|
end
|
38
38
|
|
39
39
|
option :node_config_file,
|
40
|
-
|
41
|
-
|
42
|
-
|
40
|
+
short: '-N PATH_TO_CONFIG',
|
41
|
+
long: '--node-config PATH_TO_CONFIG',
|
42
|
+
description: 'The configuration file to use on remote node'
|
43
43
|
|
44
44
|
option :splay,
|
45
|
-
|
46
|
-
|
47
|
-
|
45
|
+
long: '--splay SECONDS',
|
46
|
+
description: 'The splay time for running at intervals, in seconds',
|
47
|
+
proc: lambda { |s| s.to_i }
|
48
48
|
|
49
49
|
option :use_sudo,
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
50
|
+
long: '--[no-]sudo',
|
51
|
+
description: 'Execute the chef-client via sudo (true by default)',
|
52
|
+
boolean: true,
|
53
|
+
default: true,
|
54
|
+
proc: lambda { |v| Chef::Config[:knife][:use_sudo] = v }
|
56
55
|
|
57
56
|
option :override_runlist,
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
57
|
+
short: '-o RunlistItem,RunlistItem...',
|
58
|
+
long: '--override-runlist RunlistItem,RunlistItem...',
|
59
|
+
description: 'Replace current run list with specified items for a single run. It skips save node.json on local',
|
60
|
+
default: nil,
|
61
|
+
proc: lambda { |o| o.to_s }
|
63
62
|
|
64
63
|
option :client_version,
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
def initialize(argv=[])
|
64
|
+
long: '--client-version [latest|VERSION]',
|
65
|
+
description: 'Up or downgrade omnibus chef-client before converge.',
|
66
|
+
default: nil,
|
67
|
+
proc: lambda { |o|
|
68
|
+
if ::Knife::Zero::Helper.chef_version_available?(o)
|
69
|
+
o.to_s
|
70
|
+
else
|
71
|
+
ui.error "Client version #{o} is not found."
|
72
|
+
exit 1
|
73
|
+
end
|
74
|
+
}
|
75
|
+
|
76
|
+
def initialize(argv = [])
|
78
77
|
super
|
79
78
|
self.configure_chef
|
80
79
|
|
81
80
|
## Command hook before_converge (Before launched Chef-Zero)
|
82
81
|
if Chef::Config[:knife][:before_converge]
|
83
|
-
::Knife::Zero::Helper.hook_shell_out!(
|
82
|
+
::Knife::Zero::Helper.hook_shell_out!('before_converge', ui, Chef::Config[:knife][:before_converge])
|
84
83
|
end
|
85
84
|
|
86
85
|
validate_options!
|
@@ -91,29 +90,29 @@ class Chef
|
|
91
90
|
@name_args = [@name_args[0], start_chef_client]
|
92
91
|
end
|
93
92
|
|
94
|
-
def start_chef_client
|
93
|
+
def start_chef_client # rubocop:disable Metrics/PerceivedComplexity, Metrics/AbcSize, Metrics/CyclomaticComplexity
|
95
94
|
client_path = @config[:use_sudo] || Chef::Config[:knife][:use_sudo] ? 'sudo ' : ''
|
96
95
|
client_path = @config[:chef_client_path] ? "#{client_path}#{@config[:chef_client_path]}" : "#{client_path}chef-client"
|
97
|
-
s = String.new(
|
98
|
-
s << ' -l debug' if @config[:verbosity]
|
96
|
+
s = String.new(client_path)
|
97
|
+
s << ' -l debug' if @config[:verbosity] && @config[:verbosity] >= 2
|
99
98
|
s << " -S http://127.0.0.1:#{::Knife::Zero::Helper.zero_remote_port}"
|
100
99
|
s << " -o #{@config[:override_runlist]}" if @config[:override_runlist]
|
101
100
|
s << ' -j /etc/chef/chef_client_json.json' if @config[:json_attribs]
|
102
101
|
s << " --splay #{@config[:splay]}" if @config[:splay]
|
103
102
|
s << " -n #{@config[:named_run_list]}" if @config[:named_run_list]
|
104
103
|
s << " --config #{@config[:node_config_file]}" if @config[:node_config_file]
|
105
|
-
s <<
|
106
|
-
s <<
|
104
|
+
s << ' --skip-cookbook-sync' if @config[:skip_cookbook_sync]
|
105
|
+
s << ' --no-color' unless @config[:color]
|
107
106
|
s << " -E #{@config[:environment]}" if @config[:environment]
|
108
|
-
s <<
|
109
|
-
Chef::Log.info
|
107
|
+
s << ' -W' if @config[:why_run]
|
108
|
+
Chef::Log.info 'Remote command: ' + s
|
110
109
|
s
|
111
110
|
end
|
112
111
|
|
113
112
|
## For support policy_document_databag(old style)
|
114
113
|
def validate_options!
|
115
114
|
if override_and_named_given?
|
116
|
-
ui.error(
|
115
|
+
ui.error('--override_runlist and --named_run_list are exclusive')
|
117
116
|
exit 1
|
118
117
|
end
|
119
118
|
if json_attribs_without_override_given?
|
@@ -125,6 +124,7 @@ class Chef
|
|
125
124
|
end
|
126
125
|
true
|
127
126
|
end
|
127
|
+
|
128
128
|
# True if policy_name and run_list are both given
|
129
129
|
def override_and_named_given?
|
130
130
|
override_runlist_given? && named_run_list_given?
|
@@ -15,7 +15,7 @@ class Chef
|
|
15
15
|
Chef::Knife::ZeroChefClient.load_deps
|
16
16
|
end
|
17
17
|
|
18
|
-
banner
|
18
|
+
banner 'knife zero diagnose # show configuration from file'
|
19
19
|
|
20
20
|
def initialize(argv = [])
|
21
21
|
super
|
@@ -23,26 +23,26 @@ class Chef
|
|
23
23
|
@converge = Chef::Knife::ZeroConverge.new
|
24
24
|
end
|
25
25
|
|
26
|
-
def run
|
27
|
-
ui.msg
|
28
|
-
ui.msg
|
26
|
+
def run # rubocop:disable Metrics/AbcSize
|
27
|
+
ui.msg 'Chef::Config'
|
28
|
+
ui.msg '===================='
|
29
29
|
ui.msg Chef::Config.configuration.to_yaml
|
30
|
-
ui.msg
|
30
|
+
ui.msg ''
|
31
31
|
|
32
|
-
ui.msg
|
33
|
-
ui.msg
|
32
|
+
ui.msg 'Knife::Config'
|
33
|
+
ui.msg '===================='
|
34
34
|
ui.msg config.to_yaml
|
35
|
-
ui.msg
|
35
|
+
ui.msg ''
|
36
36
|
|
37
|
-
ui.msg
|
38
|
-
ui.msg
|
39
|
-
bootstrap = Chef::Knife::ZeroBootstrap.new
|
37
|
+
ui.msg 'Zero Bootstrap Config'
|
38
|
+
ui.msg '===================='
|
39
|
+
@bootstrap = Chef::Knife::ZeroBootstrap.new
|
40
40
|
@bootstrap.merge_configs
|
41
41
|
ui.msg @bootstrap.config.to_yaml
|
42
|
-
ui.msg
|
42
|
+
ui.msg ''
|
43
43
|
|
44
|
-
ui.msg
|
45
|
-
ui.msg
|
44
|
+
ui.msg 'Zero Converge Config'
|
45
|
+
ui.msg '===================='
|
46
46
|
@converge.merge_configs
|
47
47
|
ui.msg @converge.config.to_yaml
|
48
48
|
end
|
@@ -5,15 +5,13 @@ class Chef
|
|
5
5
|
class BootstrapSsh < Chef::Knife::Ssh
|
6
6
|
deps do
|
7
7
|
Chef::Knife::Ssh.load_deps
|
8
|
-
require
|
9
|
-
require
|
8
|
+
require 'knife-zero/net_ssh_multi_patch'
|
9
|
+
require 'knife-zero/helper'
|
10
10
|
end
|
11
11
|
|
12
|
-
def ssh_command(command, subsession=nil)
|
13
|
-
begin
|
14
|
-
|
12
|
+
def ssh_command(command, subsession = nil) # rubocop:disable Metrics/PerceivedComplexity, Metrics/AbcSize, Metrics/CyclomaticComplexity
|
15
13
|
if config[:client_version]
|
16
|
-
super(%
|
14
|
+
super(%{/opt/chef/embedded/bin/ruby -ropen-uri -e 'puts open("https://omnitruck.chef.io/install.sh").read' | sudo sh -s -- -v #{config[:client_version]}})
|
17
15
|
end
|
18
16
|
|
19
17
|
if config[:json_attribs]
|
@@ -26,25 +24,24 @@ class Chef
|
|
26
24
|
URI.parse(Chef::Config.chef_server_url).port
|
27
25
|
chef_zero_host = config[:chef_zero_host] ||
|
28
26
|
Chef::Config[:knife][:chef_zero_host] ||
|
29
|
-
|
27
|
+
'127.0.0.1'
|
30
28
|
(subsession || session).servers.each do |server|
|
31
29
|
session = server.session(true)
|
32
30
|
session.forward.remote(chef_zero_port, chef_zero_host, ::Knife::Zero::Helper.zero_remote_port)
|
33
31
|
end
|
34
32
|
super
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
end
|
33
|
+
rescue => e # rubocop:disable Style/RescueStandardError
|
34
|
+
ui.error(e.class.to_s + e.message)
|
35
|
+
ui.error e.backtrace.join("\n")
|
36
|
+
exit 1
|
40
37
|
end
|
41
38
|
|
42
39
|
def build_client_json
|
43
|
-
<<-
|
40
|
+
<<-SCRIPT
|
44
41
|
sudo sh -c 'cat <<"EOP" > /etc/chef/chef_client_json.json
|
45
42
|
#{config[:chef_client_json].to_json}
|
46
43
|
'
|
47
|
-
|
44
|
+
SCRIPT
|
48
45
|
end
|
49
46
|
end
|
50
47
|
end
|
@@ -1,80 +1,80 @@
|
|
1
1
|
require 'chef/knife/core/bootstrap_context'
|
2
|
-
require
|
2
|
+
require 'knife-zero/helper'
|
3
3
|
|
4
4
|
class Chef
|
5
5
|
class Knife
|
6
6
|
module Core
|
7
7
|
class BootstrapContext
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
8
|
+
class_eval do # rubocop:disable Metrics/BlockLength
|
9
|
+
alias_method :orig_validation_key, :validation_key
|
10
|
+
def validation_key
|
11
|
+
if @chef_config[:knife_zero]
|
12
|
+
OpenSSL::PKey::RSA.new(2048).to_s
|
13
|
+
else
|
14
|
+
orig_validation_key
|
15
|
+
end
|
16
|
+
end
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
white_lists.push([
|
25
|
-
white_list,
|
26
|
-
Chef::Config[:knife][white_list.to_sym].to_s
|
27
|
-
].join(" "))
|
28
|
-
end
|
29
|
-
end
|
30
|
-
client_rb << white_lists.join("\n")
|
18
|
+
alias_method :orig_config_content, :config_content
|
19
|
+
def config_content # rubocop:disable Metrics/AbcSize
|
20
|
+
client_rb = orig_config_content
|
21
|
+
white_lists = []
|
22
|
+
%w{automatic_attribute_whitelist default_attribute_whitelist normal_attribute_whitelist override_attribute_whitelist}.each do |white_list|
|
23
|
+
next unless Chef::Config[:knife][white_list.to_sym]&.is_a?(Array)
|
31
24
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
"policy_group \"#{@config[:policy_group]}\""].join("\n")
|
39
|
-
end
|
25
|
+
white_lists.push([
|
26
|
+
white_list,
|
27
|
+
Chef::Config[:knife][white_list.to_sym].to_s
|
28
|
+
].join(' '))
|
29
|
+
end
|
30
|
+
client_rb << white_lists.join("\n")
|
40
31
|
|
41
|
-
|
42
|
-
|
43
|
-
|
32
|
+
## For support policy_document_native_api
|
33
|
+
if @config[:policy_name]
|
34
|
+
client_rb << ["\n", 'use_policyfile true',
|
35
|
+
'versioned_cookbooks true',
|
36
|
+
'policy_document_native_api true',
|
37
|
+
"policy_name '#{@config[:policy_name]}'",
|
38
|
+
"policy_group '#{@config[:policy_group]}'"].join("\n")
|
39
|
+
end
|
44
40
|
|
45
|
-
|
46
|
-
|
41
|
+
if @config[:appendix_config]
|
42
|
+
client_rb << ["\n## --appendix-config", @config[:appendix_config]].join("\n")
|
43
|
+
end
|
47
44
|
|
48
|
-
|
49
|
-
|
50
|
-
if @chef_config[:knife_zero]
|
51
|
-
if @config[:bootstrap_converge]
|
52
|
-
client_path = @chef_config[:chef_client_path] || 'chef-client'
|
53
|
-
s = String.new("#{client_path} -j /etc/chef/first-boot.json")
|
54
|
-
s << ' -l debug' if @config[:verbosity] and @config[:verbosity] >= 2
|
55
|
-
s << " -E #{bootstrap_environment}" unless bootstrap_environment.nil?
|
56
|
-
s << " -S http://127.0.0.1:#{::Knife::Zero::Helper.zero_remote_port}"
|
57
|
-
s << " -W" if @config[:why_run]
|
58
|
-
Chef::Log.info "Remote command: " + s
|
59
|
-
s
|
60
|
-
else
|
61
|
-
"echo Execution of Chef-Client has been canceled due to bootstrap_converge is false."
|
62
|
-
end
|
63
|
-
else
|
64
|
-
orig_start_chef
|
65
|
-
end
|
66
|
-
end
|
45
|
+
client_rb
|
46
|
+
end
|
67
47
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
48
|
+
alias_method :orig_start_chef, :start_chef
|
49
|
+
def start_chef # rubocop:disable Metrics/PerceivedComplexity, Metrics/AbcSize, Metrics/CyclomaticComplexity
|
50
|
+
if @chef_config[:knife_zero]
|
51
|
+
if @config[:bootstrap_converge]
|
52
|
+
client_path = @chef_config[:chef_client_path] || 'chef-client'
|
53
|
+
s = String.new("#{client_path} -j /etc/chef/first-boot.json")
|
54
|
+
s << ' -l debug' if @config[:verbosity] && @config[:verbosity] >= 2
|
55
|
+
s << " -E #{bootstrap_environment}" unless bootstrap_environment.nil?
|
56
|
+
s << " -S http://127.0.0.1:#{::Knife::Zero::Helper.zero_remote_port}"
|
57
|
+
s << ' -W' if @config[:why_run]
|
58
|
+
Chef::Log.info 'Remote command: ' + s
|
59
|
+
s
|
60
|
+
else
|
61
|
+
'echo Execution of Chef-Client has been canceled due to bootstrap_converge is false.'
|
62
|
+
end
|
63
|
+
else
|
64
|
+
orig_start_chef
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
## For support policy_document_databag(old style)
|
69
|
+
alias_method :orig_first_boot, :first_boot
|
70
|
+
def first_boot
|
71
|
+
attributes = orig_first_boot
|
72
|
+
if @config[:policy_name]
|
73
|
+
attributes.delete(:run_list)
|
74
|
+
end
|
75
|
+
attributes
|
76
|
+
end
|
77
|
+
end
|
78
78
|
end
|
79
79
|
end
|
80
80
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'chef/knife/bootstrap/train_connector'
|
2
|
+
require 'knife-zero/helper'
|
3
|
+
|
4
|
+
class Chef
|
5
|
+
class Knife
|
6
|
+
class Bootstrap < Knife
|
7
|
+
class TrainConnector
|
8
|
+
class_eval do
|
9
|
+
def connect!
|
10
|
+
# Force connection to establish
|
11
|
+
connection.wait_until_ready
|
12
|
+
if connection.is_a? Train::Transports::SSH::Connection
|
13
|
+
connection
|
14
|
+
.instance_variable_get(:@session)
|
15
|
+
.forward.remote(
|
16
|
+
URI.parse(Chef::Config.chef_server_url).port,
|
17
|
+
'127.0.0.1', ::Knife::Zero::Helper.zero_remote_port
|
18
|
+
)
|
19
|
+
end
|
20
|
+
true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/knife-zero/helper.rb
CHANGED
@@ -3,24 +3,25 @@ require 'open-uri'
|
|
3
3
|
module Knife
|
4
4
|
module Zero
|
5
5
|
module Helper
|
6
|
-
|
7
6
|
extend Chef::Mixin::ShellOut
|
8
7
|
|
9
8
|
def self.zero_remote_port
|
10
9
|
return ::Chef::Config[:remote_chef_zero_port] if ::Chef::Config[:remote_chef_zero_port]
|
10
|
+
|
11
11
|
chef_zero_port = ::Chef::Config[:chef_zero_port] ||
|
12
12
|
::Chef::Config[:knife][:chef_zero_port] ||
|
13
13
|
8889
|
14
|
-
chef_zero_port +
|
14
|
+
chef_zero_port + 10_000
|
15
15
|
end
|
16
16
|
|
17
17
|
def self.chef_version_available?(version)
|
18
18
|
return true if version == 'latest'
|
19
|
+
|
19
20
|
chefgem_metaurl = 'https://rubygems.org/api/v1/versions/chef.json'
|
20
21
|
begin
|
21
|
-
c_versions = JSON.parse(
|
22
|
+
c_versions = JSON.parse(URI.parse(chefgem_metaurl).open.read).map { |v| v['number'] }
|
22
23
|
c_versions.include?(version)
|
23
|
-
rescue => e
|
24
|
+
rescue => e # rubocop:disable Style/RescueStandardError
|
24
25
|
puts e.inspect
|
25
26
|
puts "Some Error occurerd while fetching versions from #{chefgem_metaurl}. Please try again later."
|
26
27
|
exit
|
@@ -31,7 +32,7 @@ module Knife
|
|
31
32
|
ui.info ui.color("Execute command hook in #{event}.", :green)
|
32
33
|
begin
|
33
34
|
ui.info shell_out!(*command_args).stdout
|
34
|
-
rescue => e
|
35
|
+
rescue => e # rubocop:disable Style/RescueStandardError
|
35
36
|
ui.error e.inspect
|
36
37
|
raise e
|
37
38
|
end
|
@@ -1,7 +1,9 @@
|
|
1
1
|
require 'net/ssh/multi/version'
|
2
2
|
|
3
|
+
# rubocop:disable all
|
4
|
+
|
3
5
|
## 1.3.0~ includes this patch.
|
4
|
-
if Gem::Version.new(Net::SSH::Multi::Version::STRING) < Gem::Version.new(
|
6
|
+
if Gem::Version.new(Net::SSH::Multi::Version::STRING) < Gem::Version.new('1.3.0.rc1')
|
5
7
|
require 'net/ssh/multi'
|
6
8
|
|
7
9
|
module Net::SSH::Multi
|
@@ -122,3 +124,5 @@ module Net::SSH::Multi
|
|
122
124
|
end
|
123
125
|
end
|
124
126
|
end
|
127
|
+
|
128
|
+
# rubocop:enable all
|
data/lib/knife-zero/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: knife-zero
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- sawanoboly
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-06-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -25,7 +25,7 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.6'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: guard
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
@@ -39,7 +39,7 @@ dependencies:
|
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: guard-shell
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - ">="
|
@@ -53,7 +53,7 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: rake
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - ">="
|
@@ -67,7 +67,7 @@ dependencies:
|
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: simplecov
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - ">="
|
@@ -81,7 +81,7 @@ dependencies:
|
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
84
|
+
name: simplecov-rcov
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
87
|
- - ">="
|
@@ -95,7 +95,7 @@ dependencies:
|
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
|
-
name:
|
98
|
+
name: test-unit
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
101
|
- - ">="
|
@@ -109,7 +109,7 @@ dependencies:
|
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0'
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
|
-
name:
|
112
|
+
name: test-unit-notify
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
114
114
|
requirements:
|
115
115
|
- - ">="
|
@@ -123,7 +123,7 @@ dependencies:
|
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '0'
|
125
125
|
- !ruby/object:Gem::Dependency
|
126
|
-
name:
|
126
|
+
name: test-unit-rr
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
128
128
|
requirements:
|
129
129
|
- - ">="
|
@@ -141,9 +141,6 @@ dependencies:
|
|
141
141
|
requirement: !ruby/object:Gem::Requirement
|
142
142
|
requirements:
|
143
143
|
- - ">="
|
144
|
-
- !ruby/object:Gem::Version
|
145
|
-
version: '12.6'
|
146
|
-
- - "<"
|
147
144
|
- !ruby/object:Gem::Version
|
148
145
|
version: '15.0'
|
149
146
|
type: :runtime
|
@@ -151,9 +148,6 @@ dependencies:
|
|
151
148
|
version_requirements: !ruby/object:Gem::Requirement
|
152
149
|
requirements:
|
153
150
|
- - ">="
|
154
|
-
- !ruby/object:Gem::Version
|
155
|
-
version: '12.6'
|
156
|
-
- - "<"
|
157
151
|
- !ruby/object:Gem::Version
|
158
152
|
version: '15.0'
|
159
153
|
description: Run chef-client at remote node with chef-zero(local-mode) via HTTP over
|
@@ -175,8 +169,9 @@ files:
|
|
175
169
|
- lib/chef/knife/zero_diagnose.rb
|
176
170
|
- lib/knife-zero/bootstrap_ssh.rb
|
177
171
|
- lib/knife-zero/core/bootstrap_context.rb
|
172
|
+
- lib/knife-zero/devpatch/train_connector.rb
|
178
173
|
- lib/knife-zero/helper.rb
|
179
|
-
- lib/knife-zero/
|
174
|
+
- lib/knife-zero/net_ssh_multi_patch.rb
|
180
175
|
- lib/knife-zero/version.rb
|
181
176
|
homepage: http://knife-zero.github.io
|
182
177
|
licenses:
|
@@ -190,12 +185,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
190
185
|
requirements:
|
191
186
|
- - ">="
|
192
187
|
- !ruby/object:Gem::Version
|
193
|
-
version:
|
188
|
+
version: 2.5.0
|
194
189
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
195
190
|
requirements:
|
196
|
-
- - "
|
191
|
+
- - ">"
|
197
192
|
- !ruby/object:Gem::Version
|
198
|
-
version:
|
193
|
+
version: 1.3.1
|
199
194
|
requirements: []
|
200
195
|
rubyforge_project:
|
201
196
|
rubygems_version: 2.7.6
|