kanrisuru 0.1.0 → 0.2.1
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 +4 -4
- data/.gitignore +17 -0
- data/.rubocop.yml +47 -0
- data/.rubocop_todo.yml +0 -0
- data/CHANGELOG.md +0 -0
- data/Gemfile +2 -5
- data/LICENSE.txt +1 -1
- data/README.md +143 -7
- data/Rakefile +5 -3
- data/bin/console +4 -3
- data/kanrisuru.gemspec +21 -12
- data/lib/kanrisuru.rb +41 -2
- data/lib/kanrisuru/command.rb +99 -0
- data/lib/kanrisuru/core.rb +53 -0
- data/lib/kanrisuru/core/archive.rb +154 -0
- data/lib/kanrisuru/core/disk.rb +302 -0
- data/lib/kanrisuru/core/file.rb +332 -0
- data/lib/kanrisuru/core/find.rb +108 -0
- data/lib/kanrisuru/core/group.rb +97 -0
- data/lib/kanrisuru/core/ip.rb +1032 -0
- data/lib/kanrisuru/core/mount.rb +138 -0
- data/lib/kanrisuru/core/path.rb +140 -0
- data/lib/kanrisuru/core/socket.rb +168 -0
- data/lib/kanrisuru/core/stat.rb +104 -0
- data/lib/kanrisuru/core/stream.rb +121 -0
- data/lib/kanrisuru/core/system.rb +348 -0
- data/lib/kanrisuru/core/transfer.rb +203 -0
- data/lib/kanrisuru/core/user.rb +198 -0
- data/lib/kanrisuru/logger.rb +8 -0
- data/lib/kanrisuru/mode.rb +277 -0
- data/lib/kanrisuru/os_package.rb +235 -0
- data/lib/kanrisuru/remote.rb +10 -0
- data/lib/kanrisuru/remote/cluster.rb +95 -0
- data/lib/kanrisuru/remote/cpu.rb +68 -0
- data/lib/kanrisuru/remote/env.rb +33 -0
- data/lib/kanrisuru/remote/file.rb +354 -0
- data/lib/kanrisuru/remote/fstab.rb +412 -0
- data/lib/kanrisuru/remote/host.rb +191 -0
- data/lib/kanrisuru/remote/memory.rb +19 -0
- data/lib/kanrisuru/remote/os.rb +87 -0
- data/lib/kanrisuru/result.rb +78 -0
- data/lib/kanrisuru/template.rb +32 -0
- data/lib/kanrisuru/util.rb +40 -0
- data/lib/kanrisuru/util/bits.rb +203 -0
- data/lib/kanrisuru/util/fs_mount_opts.rb +655 -0
- data/lib/kanrisuru/util/os_family.rb +213 -0
- data/lib/kanrisuru/util/signal.rb +161 -0
- data/lib/kanrisuru/version.rb +3 -1
- data/spec/functional/core/archive_spec.rb +228 -0
- data/spec/functional/core/disk_spec.rb +80 -0
- data/spec/functional/core/file_spec.rb +341 -0
- data/spec/functional/core/find_spec.rb +52 -0
- data/spec/functional/core/group_spec.rb +65 -0
- data/spec/functional/core/ip_spec.rb +71 -0
- data/spec/functional/core/path_spec.rb +93 -0
- data/spec/functional/core/socket_spec.rb +31 -0
- data/spec/functional/core/stat_spec.rb +98 -0
- data/spec/functional/core/stream_spec.rb +99 -0
- data/spec/functional/core/system_spec.rb +96 -0
- data/spec/functional/core/transfer_spec.rb +108 -0
- data/spec/functional/core/user_spec.rb +76 -0
- data/spec/functional/os_package_spec.rb +75 -0
- data/spec/functional/remote/cluster_spec.rb +45 -0
- data/spec/functional/remote/cpu_spec.rb +41 -0
- data/spec/functional/remote/env_spec.rb +36 -0
- data/spec/functional/remote/fstab_spec.rb +76 -0
- data/spec/functional/remote/host_spec.rb +68 -0
- data/spec/functional/remote/memory_spec.rb +29 -0
- data/spec/functional/remote/os_spec.rb +63 -0
- data/spec/functional/remote/remote_file_spec.rb +180 -0
- data/spec/helper/test_hosts.rb +68 -0
- data/spec/hosts.json +92 -0
- data/spec/spec_helper.rb +11 -3
- data/spec/unit/fstab_spec.rb +22 -0
- data/spec/unit/kanrisuru_spec.rb +9 -0
- data/spec/unit/mode_spec.rb +183 -0
- data/spec/unit/template_spec.rb +13 -0
- data/spec/unit/util_spec.rb +177 -0
- data/spec/zz_reboot_spec.rb +46 -0
- metadata +136 -13
- data/spec/kanrisuru_spec.rb +0 -9
@@ -0,0 +1,235 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
module Kanrisuru
|
6
|
+
module OsPackage
|
7
|
+
module Define
|
8
|
+
def self.extended(base)
|
9
|
+
base.instance_variable_set(:@os_method_properties, {})
|
10
|
+
base.instance_variable_set(:@os_methods, Set.new)
|
11
|
+
base.instance_variable_set(:@os_method_cache, {})
|
12
|
+
end
|
13
|
+
|
14
|
+
def os_define(os_name, method_name, options = {})
|
15
|
+
unique_method_name = options[:alias] || method_name
|
16
|
+
|
17
|
+
@os_methods.add(method_name)
|
18
|
+
|
19
|
+
if @os_method_properties.key?(unique_method_name)
|
20
|
+
params = {
|
21
|
+
os_name: os_name.to_s,
|
22
|
+
method_name: method_name,
|
23
|
+
options: options
|
24
|
+
}
|
25
|
+
|
26
|
+
@os_method_properties[unique_method_name].prepend(params)
|
27
|
+
else
|
28
|
+
@os_method_properties[unique_method_name] = [{
|
29
|
+
os_name: os_name.to_s,
|
30
|
+
method_name: method_name,
|
31
|
+
options: options
|
32
|
+
}]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
module Collection
|
38
|
+
def os_collection(mod, opts = {})
|
39
|
+
os_method_properties = mod.instance_variable_get(:@os_method_properties)
|
40
|
+
os_method_names = os_method_properties.keys
|
41
|
+
|
42
|
+
namespace = opts[:namespace]
|
43
|
+
namespace_instance = nil
|
44
|
+
|
45
|
+
if namespace
|
46
|
+
## Define the namespace as an eigen class instance on the host.
|
47
|
+
## Namespaced instances will access core host methods
|
48
|
+
## with @host instance variable.
|
49
|
+
namespace_class = Kanrisuru::Remote::Cluster.const_set(Kanrisuru::Util.camelize(namespace), Class.new)
|
50
|
+
namespace_instance = Kanrisuru::Remote::Cluster.instance_variable_set("@#{namespace}", namespace_class.new)
|
51
|
+
|
52
|
+
class_eval do
|
53
|
+
define_method namespace do
|
54
|
+
namespace_instance.instance_variable_set(:@cluster, self)
|
55
|
+
namespace_instance
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
namespace_class.class_eval do
|
60
|
+
os_method_names.each do |method_name|
|
61
|
+
define_method method_name do |*args, &block|
|
62
|
+
cluster = namespace_instance.instance_variable_get(:@cluster)
|
63
|
+
hosts = cluster.instance_variable_get(:@hosts)
|
64
|
+
hosts.map do |host_addr, host|
|
65
|
+
{ host: host_addr, result: host.send(namespace).send(method_name, *args, &block) }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
else
|
71
|
+
class_eval do
|
72
|
+
os_method_names.each do |method_name|
|
73
|
+
define_method method_name do |*args, &block|
|
74
|
+
@hosts.map do |host_addr, host|
|
75
|
+
{ host: host_addr, result: host.send(method_name, *args, &block) }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
module Include
|
85
|
+
def os_include(mod, opts = {})
|
86
|
+
os_method_properties = mod.instance_variable_get(:@os_method_properties)
|
87
|
+
os_method_names = os_method_properties.keys
|
88
|
+
|
89
|
+
## Need to encapsulate any helper methods called in the module
|
90
|
+
## to bind to the host instance. This acts as a psudeo module include.
|
91
|
+
os_methods = mod.instance_variable_get(:@os_methods)
|
92
|
+
|
93
|
+
public_methods = mod.instance_methods(false) - os_methods.to_a
|
94
|
+
private_methods = mod.private_instance_methods(false)
|
95
|
+
protected_methods = mod.protected_instance_methods(false)
|
96
|
+
include_methods = (public_methods + protected_methods + private_methods).flatten
|
97
|
+
|
98
|
+
include_method_bindings = proc do
|
99
|
+
define_method 'os_method_cache' do
|
100
|
+
@os_method_cache ||= {}
|
101
|
+
end
|
102
|
+
|
103
|
+
private :os_method_cache
|
104
|
+
|
105
|
+
include_methods.each do |method_name|
|
106
|
+
define_method method_name do |*args, &block|
|
107
|
+
unbound_method = mod.instance_method(method_name)
|
108
|
+
bind_method(unbound_method, *args, &block)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
private_methods.each { |method_name| private(method_name) }
|
113
|
+
protected_methods.each { |method_name| protected(method_name) }
|
114
|
+
|
115
|
+
private
|
116
|
+
if RUBY_VERSION < '2.7'
|
117
|
+
define_method 'bind_method' do |unbound_method, *args, &block|
|
118
|
+
unbound_method.bind(self).call(*args, &block)
|
119
|
+
end
|
120
|
+
else
|
121
|
+
define_method 'bind_method' do |unbound_method, *args, &block|
|
122
|
+
unbound_method.bind_call(self, *args, &block)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
namespace = opts[:namespace]
|
128
|
+
namespace_class = nil
|
129
|
+
namespace_instance = nil
|
130
|
+
|
131
|
+
if namespace
|
132
|
+
## Define the namespace as an eigen class instance within the host class.
|
133
|
+
## Namespaced instances will access core host methods
|
134
|
+
## with @host instance variable.
|
135
|
+
namespace_class = Kanrisuru::Remote::Host.const_set(Kanrisuru::Util.camelize(namespace), Class.new)
|
136
|
+
namespace_instance = Kanrisuru::Remote::Host.instance_variable_set("@#{namespace}", namespace_class.new)
|
137
|
+
|
138
|
+
namespace_class.class_eval(&include_method_bindings)
|
139
|
+
|
140
|
+
class_eval do
|
141
|
+
define_method namespace do
|
142
|
+
namespace_instance.instance_variable_set(:@host, self)
|
143
|
+
namespace_instance
|
144
|
+
end
|
145
|
+
end
|
146
|
+
else
|
147
|
+
class_eval(&include_method_bindings)
|
148
|
+
end
|
149
|
+
|
150
|
+
class_eval do
|
151
|
+
os_method_names.each do |method_name|
|
152
|
+
if namespace
|
153
|
+
namespace_class.class_eval do
|
154
|
+
define_method method_name do |*args, &block|
|
155
|
+
unbound_method = nil
|
156
|
+
|
157
|
+
if os_method_cache.key?(method_name)
|
158
|
+
unbound_method = os_method_cache[method_name]
|
159
|
+
else
|
160
|
+
host = namespace_instance.instance_variable_get(:@host)
|
161
|
+
|
162
|
+
## Find the correct method to resolve based on the OS for the remote host.
|
163
|
+
defined_method_name = host.resolve_os_method_name(os_method_properties, method_name)
|
164
|
+
unless defined_method_name
|
165
|
+
raise NoMethodError, "undefined method `#{method_name}' for #{self.class}"
|
166
|
+
end
|
167
|
+
|
168
|
+
## Get reference to the unbound method defined in module
|
169
|
+
unbound_method = mod.instance_method(defined_method_name)
|
170
|
+
raise NoMethodError, "undefined method `#{method_name}' for #{self.class}" unless unbound_method
|
171
|
+
|
172
|
+
## Cache the unbound method on this host instance for faster resolution on
|
173
|
+
## the next invocation of this method
|
174
|
+
os_method_cache[method_name] = unbound_method
|
175
|
+
end
|
176
|
+
|
177
|
+
## Bind the method to host instance and
|
178
|
+
## call it with args and block
|
179
|
+
bind_method(unbound_method, *args, &block)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
else
|
183
|
+
define_method method_name do |*args, &block|
|
184
|
+
unbound_method = nil
|
185
|
+
|
186
|
+
if os_method_cache.key?(method_name)
|
187
|
+
unbound_method = os_method_cache[method_name]
|
188
|
+
else
|
189
|
+
host = self
|
190
|
+
|
191
|
+
## Find the correct method to resolve based on the OS for the remote host.
|
192
|
+
defined_method_name = host.resolve_os_method_name(os_method_properties, method_name)
|
193
|
+
raise NoMethodError, "undefined method `#{method_name}' for #{self.class}" unless defined_method_name
|
194
|
+
|
195
|
+
## Get reference to the unbound method defined in module
|
196
|
+
unbound_method = mod.instance_method(defined_method_name)
|
197
|
+
raise NoMethodError, "undefined method `#{method_name}' for #{self.class}" unless unbound_method
|
198
|
+
|
199
|
+
## Cache the unbound method on this host instance for faster resolution on
|
200
|
+
## the next invocation of this method
|
201
|
+
os_method_cache[method_name] = unbound_method
|
202
|
+
end
|
203
|
+
|
204
|
+
## Bind the method to host instance and
|
205
|
+
## call it with args and block
|
206
|
+
bind_method(unbound_method, *args, &block)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def resolve_os_method_name(properties, method_name)
|
212
|
+
kernel = os.kernel.downcase
|
213
|
+
release = os.release.downcase
|
214
|
+
|
215
|
+
properties[method_name].each do |property|
|
216
|
+
os_name = property[:os_name]
|
217
|
+
strict = property[:options] ? property[:options][:strict] : false
|
218
|
+
except = property[:options] ? property[:options][:except] : ''
|
219
|
+
|
220
|
+
next if except && (except == release || except.include?(release))
|
221
|
+
|
222
|
+
if release == os_name || kernel == os_name ||
|
223
|
+
(Kanrisuru::Util::OsFamily.family_include_distribution?(os_name, release) && !strict) ||
|
224
|
+
(Kanrisuru::Util::OsFamily.upstream_include_distribution?(os_name, release) && !strict)
|
225
|
+
return property[:method_name]
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
nil
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'remote/host'
|
4
|
+
require_relative 'remote/cluster'
|
5
|
+
require_relative 'remote/os'
|
6
|
+
require_relative 'remote/env'
|
7
|
+
require_relative 'remote/memory'
|
8
|
+
require_relative 'remote/cpu'
|
9
|
+
require_relative 'remote/file'
|
10
|
+
require_relative 'remote/fstab'
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kanrisuru
|
4
|
+
module Remote
|
5
|
+
class Cluster
|
6
|
+
extend OsPackage::Collection
|
7
|
+
include Enumerable
|
8
|
+
|
9
|
+
def initialize(hosts)
|
10
|
+
@hosts = {}
|
11
|
+
hosts.each do |host_opts|
|
12
|
+
add_host(host_opts)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def [](hostname)
|
17
|
+
@hosts[hostname]
|
18
|
+
end
|
19
|
+
|
20
|
+
def <<(host_opts)
|
21
|
+
add_host(host_opts)
|
22
|
+
end
|
23
|
+
|
24
|
+
def execute(command)
|
25
|
+
@hosts.map do |host_addr, host|
|
26
|
+
## Need to evaluate each host independently for the command.
|
27
|
+
cmd = Kanrisuru::Command.new(command)
|
28
|
+
|
29
|
+
{ host: host_addr, result: host.execute(cmd) }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def execute_shell(command)
|
34
|
+
@hosts.map do |host_addr, host|
|
35
|
+
## Need to evaluate each host independently for the command.
|
36
|
+
cmd = Kanrisuru::Command.new(command)
|
37
|
+
|
38
|
+
{ host: host_addr, result: host.execute_shell(cmd) }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def each(&block)
|
43
|
+
@hosts.each { |_host_addr, host| block.call(host) }
|
44
|
+
end
|
45
|
+
|
46
|
+
def hostname
|
47
|
+
map_host_results(:hostname)
|
48
|
+
end
|
49
|
+
|
50
|
+
def ping?
|
51
|
+
map_host_results(:ping?)
|
52
|
+
end
|
53
|
+
|
54
|
+
def su(user)
|
55
|
+
@hosts.each do |_host_addr, host|
|
56
|
+
host.su(user)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def chdir(path = '~')
|
61
|
+
cd(path)
|
62
|
+
end
|
63
|
+
|
64
|
+
def cd(path = '~')
|
65
|
+
@hosts.each do |_host_addr, host|
|
66
|
+
host.cd(path)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def disconnect
|
71
|
+
@hosts.each do |_host_addr, host|
|
72
|
+
host.disconnect
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def map_host_results(action)
|
79
|
+
@hosts.map do |host_addr, host|
|
80
|
+
{ host: host_addr, result: host.send(action) }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def add_host(host_opts)
|
85
|
+
if host_opts.instance_of?(Hash)
|
86
|
+
@hosts[host_opts[:host]] = Kanrisuru::Remote::Host.new(host_opts)
|
87
|
+
elsif host_opts.instance_of?(Kanrisuru::Remote::Host)
|
88
|
+
@hosts[host_opts.host] = host_opts
|
89
|
+
else
|
90
|
+
raise 'Not a valid host option'
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kanrisuru
|
4
|
+
module Remote
|
5
|
+
class Cpu
|
6
|
+
def initialize(host)
|
7
|
+
@host = host
|
8
|
+
|
9
|
+
raise 'Not implemented' unless @host.os && @host.os.kernel == 'Linux'
|
10
|
+
|
11
|
+
initialize_linux
|
12
|
+
end
|
13
|
+
|
14
|
+
def load_average
|
15
|
+
@host.load_average.to_a
|
16
|
+
end
|
17
|
+
|
18
|
+
def load_average1
|
19
|
+
load_average[0]
|
20
|
+
end
|
21
|
+
|
22
|
+
def load_average5
|
23
|
+
load_average[1]
|
24
|
+
end
|
25
|
+
|
26
|
+
def load_average15
|
27
|
+
load_average[2]
|
28
|
+
end
|
29
|
+
|
30
|
+
def sockets
|
31
|
+
@sockets_count
|
32
|
+
end
|
33
|
+
|
34
|
+
def cores
|
35
|
+
@logical_cores_count
|
36
|
+
end
|
37
|
+
|
38
|
+
def total
|
39
|
+
@logical_cores_count
|
40
|
+
end
|
41
|
+
|
42
|
+
def count
|
43
|
+
@logical_cores_count
|
44
|
+
end
|
45
|
+
|
46
|
+
def threads_per_core
|
47
|
+
@threads_per_core_count
|
48
|
+
end
|
49
|
+
|
50
|
+
def cores_per_socket
|
51
|
+
@cores_per_socket_count
|
52
|
+
end
|
53
|
+
|
54
|
+
def hyperthreading?
|
55
|
+
threads_per_core > 1
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def initialize_linux
|
61
|
+
@sockets_count = @host.cpu_info('sockets').to_i
|
62
|
+
@cores_per_socket_count = @host.cpu_info('cores_per_socket').to_i
|
63
|
+
@threads_per_core_count = @host.cpu_info('threads').to_i
|
64
|
+
@logical_cores_count = @host.cpu_info('cores').to_i
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kanrisuru
|
4
|
+
module Remote
|
5
|
+
class Env
|
6
|
+
def initialize
|
7
|
+
@env = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_h
|
11
|
+
@env
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
string = ''
|
16
|
+
@env.each.with_index do |(key, value), index|
|
17
|
+
string += "export #{key}=#{value};"
|
18
|
+
string += ' ' if index < @env.length - 1
|
19
|
+
end
|
20
|
+
|
21
|
+
string
|
22
|
+
end
|
23
|
+
|
24
|
+
def [](key)
|
25
|
+
@env[key.to_s.upcase]
|
26
|
+
end
|
27
|
+
|
28
|
+
def []=(key, value)
|
29
|
+
@env[key.to_s.upcase] = value
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,354 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
module Kanrisuru
|
6
|
+
module Remote
|
7
|
+
class File
|
8
|
+
include Enumerable
|
9
|
+
|
10
|
+
WRITE_LINE_COUNT = 5_000
|
11
|
+
READ_FILE_SIZE = 3_000_000
|
12
|
+
|
13
|
+
attr_reader :mode, :blocks, :size, :type, :gid, :group, :uid, :user, :inode, :last_access, :last_modified,
|
14
|
+
:last_changed, :lines, :words, :chars, :path
|
15
|
+
|
16
|
+
def initialize(path, host, backup = nil)
|
17
|
+
@path = path
|
18
|
+
@host = host
|
19
|
+
@backup = backup
|
20
|
+
|
21
|
+
@file_lines = []
|
22
|
+
|
23
|
+
reload! if exists?
|
24
|
+
end
|
25
|
+
|
26
|
+
def expand_path
|
27
|
+
return '' unless exists?
|
28
|
+
|
29
|
+
result = @host.realpath(@path)
|
30
|
+
result.success? ? result.path : ''
|
31
|
+
end
|
32
|
+
|
33
|
+
def exists?
|
34
|
+
@host.inode?(@path)
|
35
|
+
end
|
36
|
+
|
37
|
+
def file?
|
38
|
+
@host.file?(@path)
|
39
|
+
end
|
40
|
+
|
41
|
+
def [](index)
|
42
|
+
if chunk_read_file? && index < @lines
|
43
|
+
chunk_read_line_by_index(index)
|
44
|
+
elsif index < @lines
|
45
|
+
@file_lines[index]
|
46
|
+
else
|
47
|
+
raise ArgumentError, "Out of bounds index access for #{index}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def dir?
|
52
|
+
@host.dir?(@path)
|
53
|
+
end
|
54
|
+
|
55
|
+
def zero?
|
56
|
+
@host.empty_file?(@path)
|
57
|
+
end
|
58
|
+
|
59
|
+
def chmod(mode)
|
60
|
+
result = @host.chmod(@path, mode)
|
61
|
+
update_file_attributes(result) if result.success?
|
62
|
+
end
|
63
|
+
|
64
|
+
def chown(owner, group)
|
65
|
+
result = @host.chown(@path, owner: owner, group: group)
|
66
|
+
update_file_attributes(result) if result.success?
|
67
|
+
end
|
68
|
+
|
69
|
+
def writeable?
|
70
|
+
return @writeable if [true, false].include?(@writeable)
|
71
|
+
|
72
|
+
if !file? && !zero?
|
73
|
+
@writeable = false
|
74
|
+
return false
|
75
|
+
end
|
76
|
+
|
77
|
+
current_user = @host.remote_user
|
78
|
+
result = @host.get_user(current_user)
|
79
|
+
|
80
|
+
raise 'Invalid result' unless result.success?
|
81
|
+
|
82
|
+
groups = result.groups.map(&:name)
|
83
|
+
|
84
|
+
@writeable = @mode.other.write? ||
|
85
|
+
(@mode.group.write? && groups.include?(@group)) ||
|
86
|
+
(@mode.owner.write? && @user == current_user)
|
87
|
+
end
|
88
|
+
|
89
|
+
def find_and_replace_value(regex, value)
|
90
|
+
new_lines = []
|
91
|
+
|
92
|
+
each do |existing_line|
|
93
|
+
new_lines << if regex.match(existing_line)
|
94
|
+
existing_line.gsub(regex, value)
|
95
|
+
else
|
96
|
+
existing_line
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
write_lines_to_file(new_lines, 'write')
|
101
|
+
end
|
102
|
+
|
103
|
+
def find_and_replace_line(regex, new_line)
|
104
|
+
new_lines = []
|
105
|
+
|
106
|
+
each do |existing_line|
|
107
|
+
new_lines << if regex.match(existing_line)
|
108
|
+
new_line
|
109
|
+
else
|
110
|
+
existing_line
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
write_lines_to_file(new_lines, 'write')
|
115
|
+
end
|
116
|
+
|
117
|
+
def find_and_append(regex, new_line)
|
118
|
+
new_lines = []
|
119
|
+
|
120
|
+
each do |existing_line|
|
121
|
+
new_lines << existing_line
|
122
|
+
new_lines << new_line if regex.match(existing_line)
|
123
|
+
end
|
124
|
+
|
125
|
+
write_lines_to_file(new_lines, 'write')
|
126
|
+
end
|
127
|
+
|
128
|
+
def find_and_prepend(regex, new_line)
|
129
|
+
new_lines = []
|
130
|
+
|
131
|
+
each do |existing_line|
|
132
|
+
new_lines << new_line if regex.match(existing_line)
|
133
|
+
new_lines << existing_line
|
134
|
+
end
|
135
|
+
|
136
|
+
write_lines_to_file(new_lines, 'write')
|
137
|
+
end
|
138
|
+
|
139
|
+
def find_and_remove(regex)
|
140
|
+
new_lines = []
|
141
|
+
|
142
|
+
each do |existing_line|
|
143
|
+
new_lines << existing_line unless regex.match(existing_line)
|
144
|
+
end
|
145
|
+
|
146
|
+
write_lines_to_file(new_lines, 'write')
|
147
|
+
end
|
148
|
+
|
149
|
+
def write(&block)
|
150
|
+
return unless writeable?
|
151
|
+
|
152
|
+
new_lines = []
|
153
|
+
block.call(new_lines)
|
154
|
+
|
155
|
+
backup_file if should_backup?
|
156
|
+
write_lines_to_file(new_lines, 'write')
|
157
|
+
end
|
158
|
+
|
159
|
+
def append(&block)
|
160
|
+
return unless writeable?
|
161
|
+
|
162
|
+
new_lines = []
|
163
|
+
block.call(new_lines)
|
164
|
+
|
165
|
+
backup_file if should_backup?
|
166
|
+
write_lines_to_file(new_lines, 'append')
|
167
|
+
end
|
168
|
+
|
169
|
+
def prepend(&block)
|
170
|
+
return unless writeable?
|
171
|
+
|
172
|
+
new_lines = []
|
173
|
+
block.call(new_lines)
|
174
|
+
|
175
|
+
backup_file if should_backup?
|
176
|
+
|
177
|
+
## If large file, use tmp file to prepend to
|
178
|
+
if chunk_read_file?
|
179
|
+
tmp_file = "/tmp/kanrisuru-tempfile-#{Time.now.strftime('%Y-%m-%d-%H-%M-%S-%L')}"
|
180
|
+
|
181
|
+
@host.echo(new_lines.join('\\n'), new_file: tmp_file, backslash: true, mode: 'write')
|
182
|
+
@host.cat(@path, new_file: tmp_file, mode: 'append')
|
183
|
+
@host.cp(tmp_file, @path)
|
184
|
+
|
185
|
+
@host.rm(tmp_file)
|
186
|
+
reload!
|
187
|
+
else
|
188
|
+
all_lines = new_lines + @file_lines
|
189
|
+
write_lines_to_file(all_lines, 'write')
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def rename(new_path)
|
194
|
+
# @host.mv()
|
195
|
+
end
|
196
|
+
|
197
|
+
def touch
|
198
|
+
result = @host.touch(@path)
|
199
|
+
update_file_attributes(result[0]) if result.success?
|
200
|
+
end
|
201
|
+
|
202
|
+
def delete
|
203
|
+
result = @host.unlink(@path)
|
204
|
+
|
205
|
+
init_file_attirbutes if result.success?
|
206
|
+
|
207
|
+
result.success?
|
208
|
+
end
|
209
|
+
|
210
|
+
def each(&block)
|
211
|
+
if chunk_read_file?
|
212
|
+
each_chunk(&block)
|
213
|
+
else
|
214
|
+
@file_lines.each { |line| block.call(line) }
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def reload!
|
219
|
+
@writeable = nil
|
220
|
+
@file_lines = []
|
221
|
+
|
222
|
+
wc_file
|
223
|
+
stat_file
|
224
|
+
read
|
225
|
+
end
|
226
|
+
|
227
|
+
private
|
228
|
+
|
229
|
+
def should_backup?
|
230
|
+
Kanrisuru::Util.present?(@backup)
|
231
|
+
end
|
232
|
+
|
233
|
+
def backup_file
|
234
|
+
result = @host.cp(@path, @backup)
|
235
|
+
raise "Backup of #{@path} failed" if result.failure?
|
236
|
+
end
|
237
|
+
|
238
|
+
def each_chunk(&block)
|
239
|
+
chunk_size = @lines >> 1
|
240
|
+
index = 1
|
241
|
+
|
242
|
+
loop do
|
243
|
+
result = @host.read_file_chunk(@path, index, index + chunk_size - 1)
|
244
|
+
break if result.failure?
|
245
|
+
|
246
|
+
lines = result.data
|
247
|
+
lines.each { |line| block.call(line) }
|
248
|
+
|
249
|
+
break if index >= @lines
|
250
|
+
|
251
|
+
index += chunk_size
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def read
|
256
|
+
return if chunk_read_file?
|
257
|
+
|
258
|
+
result = @host.cat(@path)
|
259
|
+
raise 'Error reading remote file' if result.failure?
|
260
|
+
|
261
|
+
@file_lines = result.data
|
262
|
+
end
|
263
|
+
|
264
|
+
def write_lines_to_file(lines, mode)
|
265
|
+
if lines.length < WRITE_LINE_COUNT
|
266
|
+
write_to_file(lines, mode)
|
267
|
+
else
|
268
|
+
chunk_write_to_file(lines, mode)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
def write_to_file(lines, mode)
|
273
|
+
result = @host.echo(lines.join('\\n'), new_file: @path, backslash: true, mode: mode)
|
274
|
+
|
275
|
+
reload! if result.success?
|
276
|
+
end
|
277
|
+
|
278
|
+
def chunk_write_to_file(lines, mode)
|
279
|
+
## Clear initial file to write new data
|
280
|
+
if mode == 'write'
|
281
|
+
@host.unlink(@path)
|
282
|
+
@host.touch(@path)
|
283
|
+
end
|
284
|
+
|
285
|
+
lines.each_slice(WRITE_LINE_COUNT) do |lines_slice|
|
286
|
+
result = @host.echo(lines_slice.join('\\n'), new_file: @path, backslash: true, mode: 'append')
|
287
|
+
raise 'Error appending lines to file' if result.failure?
|
288
|
+
end
|
289
|
+
|
290
|
+
reload!
|
291
|
+
end
|
292
|
+
|
293
|
+
def chunk_read_file?
|
294
|
+
@size >= READ_FILE_SIZE
|
295
|
+
end
|
296
|
+
|
297
|
+
def chunk_read_line_by_index(index)
|
298
|
+
result = @host.read_file_chunk(@path, index + 1, index + 1)
|
299
|
+
result.success? ? result[0] : nil
|
300
|
+
end
|
301
|
+
|
302
|
+
def wc_file
|
303
|
+
result = @host.wc(@path)
|
304
|
+
|
305
|
+
return if result.failure?
|
306
|
+
|
307
|
+
@lines = result.lines
|
308
|
+
@words = result.words
|
309
|
+
@chars = result.characters
|
310
|
+
end
|
311
|
+
|
312
|
+
def stat_file
|
313
|
+
result = @host.stat(@path)
|
314
|
+
|
315
|
+
update_file_attributes(result) if result.success?
|
316
|
+
end
|
317
|
+
|
318
|
+
def update_file_attributes(result)
|
319
|
+
@mode = result.mode
|
320
|
+
@blocks = result.blocks
|
321
|
+
@size = result.fsize
|
322
|
+
@type = result.file_type
|
323
|
+
@gid = result.gid
|
324
|
+
@group = result.group
|
325
|
+
@uid = result.uid
|
326
|
+
@user = result.user
|
327
|
+
@inode = result.inode
|
328
|
+
@last_access = result.last_access
|
329
|
+
@last_modified = result.last_modified
|
330
|
+
@last_changed = result.last_changed
|
331
|
+
end
|
332
|
+
|
333
|
+
def init_file_attirbutes
|
334
|
+
@writeable = nil
|
335
|
+
@mode = nil
|
336
|
+
@blocks = nil
|
337
|
+
@size = nil
|
338
|
+
@type = nil
|
339
|
+
@gid = nil
|
340
|
+
@group = nil
|
341
|
+
@uid = nil
|
342
|
+
@user = nil
|
343
|
+
@inode = nil
|
344
|
+
@last_access = nil
|
345
|
+
@last_modified = nil
|
346
|
+
@last_changed = nil
|
347
|
+
|
348
|
+
@lines = nil
|
349
|
+
@words = nil
|
350
|
+
@chars = nil
|
351
|
+
end
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|