haveapi-fs 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.yardopts +1 -0
- data/CHANGELOG +2 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +21 -0
- data/README.md +338 -0
- data/Rakefile +1 -0
- data/assets/css/bootstrap.min.css +6 -0
- data/assets/css/local.css +11 -0
- data/bin/haveapi-fs +5 -0
- data/haveapi-fs.gemspec +30 -0
- data/lib/core_ext/string.rb +9 -0
- data/lib/haveapi/fs/auth/base.rb +56 -0
- data/lib/haveapi/fs/auth/basic.rb +29 -0
- data/lib/haveapi/fs/auth/noauth.rb +9 -0
- data/lib/haveapi/fs/auth/token.rb +39 -0
- data/lib/haveapi/fs/cache.rb +71 -0
- data/lib/haveapi/fs/cleaner.rb +56 -0
- data/lib/haveapi/fs/component.rb +237 -0
- data/lib/haveapi/fs/components/action_dir.rb +106 -0
- data/lib/haveapi/fs/components/action_errors.rb +49 -0
- data/lib/haveapi/fs/components/action_exec.rb +12 -0
- data/lib/haveapi/fs/components/action_exec_edit.rb +90 -0
- data/lib/haveapi/fs/components/action_input.rb +40 -0
- data/lib/haveapi/fs/components/action_message.rb +19 -0
- data/lib/haveapi/fs/components/action_output.rb +79 -0
- data/lib/haveapi/fs/components/action_status.rb +32 -0
- data/lib/haveapi/fs/components/cache_stats.rb +19 -0
- data/lib/haveapi/fs/components/component_list.rb +24 -0
- data/lib/haveapi/fs/components/create_action_dir.rb +15 -0
- data/lib/haveapi/fs/components/delete_action_dir.rb +22 -0
- data/lib/haveapi/fs/components/directory.rb +45 -0
- data/lib/haveapi/fs/components/directory_reset.rb +8 -0
- data/lib/haveapi/fs/components/executable.rb +75 -0
- data/lib/haveapi/fs/components/file.rb +43 -0
- data/lib/haveapi/fs/components/groff_help_file.rb +9 -0
- data/lib/haveapi/fs/components/help_file.rb +28 -0
- data/lib/haveapi/fs/components/html_help_file.rb +24 -0
- data/lib/haveapi/fs/components/index_filter.rb +63 -0
- data/lib/haveapi/fs/components/info_files.rb +19 -0
- data/lib/haveapi/fs/components/instance_create.rb +20 -0
- data/lib/haveapi/fs/components/instance_edit.rb +49 -0
- data/lib/haveapi/fs/components/list_item.rb +28 -0
- data/lib/haveapi/fs/components/md_help_file.rb +24 -0
- data/lib/haveapi/fs/components/meta_dir.rb +42 -0
- data/lib/haveapi/fs/components/meta_file.rb +21 -0
- data/lib/haveapi/fs/components/parameter.rb +132 -0
- data/lib/haveapi/fs/components/pry.rb +9 -0
- data/lib/haveapi/fs/components/remote_control_file.rb +92 -0
- data/lib/haveapi/fs/components/resource_action_dir.rb +72 -0
- data/lib/haveapi/fs/components/resource_dir.rb +161 -0
- data/lib/haveapi/fs/components/resource_id.rb +15 -0
- data/lib/haveapi/fs/components/resource_instance_dir.rb +146 -0
- data/lib/haveapi/fs/components/rfuse_check.rb +3 -0
- data/lib/haveapi/fs/components/root.rb +75 -0
- data/lib/haveapi/fs/components/save_instance.rb +11 -0
- data/lib/haveapi/fs/components/unsaved_list.rb +24 -0
- data/lib/haveapi/fs/components/update_action_dir.rb +31 -0
- data/lib/haveapi/fs/context.rb +43 -0
- data/lib/haveapi/fs/exceptions.rb +0 -0
- data/lib/haveapi/fs/factory.rb +59 -0
- data/lib/haveapi/fs/fs.rb +198 -0
- data/lib/haveapi/fs/help.rb +91 -0
- data/lib/haveapi/fs/main.rb +134 -0
- data/lib/haveapi/fs/remote_control.rb +29 -0
- data/lib/haveapi/fs/version.rb +5 -0
- data/lib/haveapi/fs/worker.rb +77 -0
- data/lib/haveapi/fs.rb +65 -0
- data/templates/help/html/action_dir.erb +33 -0
- data/templates/help/html/action_errors.erb +4 -0
- data/templates/help/html/action_input.erb +40 -0
- data/templates/help/html/action_output.erb +21 -0
- data/templates/help/html/index_filter.erb +16 -0
- data/templates/help/html/layout.erb +45 -0
- data/templates/help/html/resource_action_dir.erb +18 -0
- data/templates/help/html/resource_dir.erb +18 -0
- data/templates/help/html/resource_instance_dir.erb +64 -0
- data/templates/help/html/root.erb +42 -0
- data/templates/help/md/action_dir.erb +29 -0
- data/templates/help/md/action_errors.erb +2 -0
- data/templates/help/md/action_input.erb +23 -0
- data/templates/help/md/action_output.erb +11 -0
- data/templates/help/md/index_filter.erb +11 -0
- data/templates/help/md/layout.erb +14 -0
- data/templates/help/md/resource_action_dir.erb +10 -0
- data/templates/help/md/resource_dir.erb +15 -0
- data/templates/help/md/resource_instance_dir.erb +42 -0
- data/templates/help/md/root.erb +34 -0
- metadata +231 -0
@@ -0,0 +1,59 @@
|
|
1
|
+
module HaveAPI::Fs
|
2
|
+
# The Factory is used to create instances of new components and can be used
|
3
|
+
# to replace specific components by different ones, thus allowing to change
|
4
|
+
# their behaviour.
|
5
|
+
class Factory
|
6
|
+
class << self
|
7
|
+
# @!attribute replacements
|
8
|
+
# @return [Hash] collection of all replacements
|
9
|
+
attr_accessor :replacements
|
10
|
+
|
11
|
+
# Replace a component class by a different class. This method has two
|
12
|
+
# forms. Either call it with a hash of replacements or with two arguments,
|
13
|
+
# where the first is the class to be replaced and the second the class
|
14
|
+
# to replace it with.
|
15
|
+
def replace(*args)
|
16
|
+
@replacements ||= {}
|
17
|
+
|
18
|
+
if args.size == 2
|
19
|
+
@replacements[args[0]] = args[1]
|
20
|
+
|
21
|
+
else
|
22
|
+
@replacements.update(args.first)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Resolves the class for component `klass`, i.e. it checks if there is a
|
27
|
+
# replacement for `klass` to return instead.
|
28
|
+
#
|
29
|
+
# @return [Class]
|
30
|
+
def component(klass)
|
31
|
+
if @replacements && @replacements.has_key?(klass)
|
32
|
+
@replacements[klass]
|
33
|
+
|
34
|
+
else
|
35
|
+
klass
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Create a new component with `klass` and its constructor's arguments
|
40
|
+
# in `args`.
|
41
|
+
#
|
42
|
+
# @param [HaveAPI::Fs::Context] context
|
43
|
+
# @param [Symbol] name
|
44
|
+
# @param [Class] klass
|
45
|
+
# @param [Array] args
|
46
|
+
# @return [Component]
|
47
|
+
def create(context, name, klass, *args)
|
48
|
+
child = component(klass).new(*args)
|
49
|
+
c_name = klass.component || klass.name.split('::').last.underscore.to_sym
|
50
|
+
|
51
|
+
child.context = context.clone
|
52
|
+
child.context[c_name] = child
|
53
|
+
child.context.file_path << name.to_s if name
|
54
|
+
child.setup
|
55
|
+
child
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'rfusefs'
|
3
|
+
require 'haveapi/client'
|
4
|
+
|
5
|
+
module HaveAPI::Fs
|
6
|
+
# Interface with RFuseFS API. Methods called by RFuseFS are redirected to
|
7
|
+
# appropriate components.
|
8
|
+
class Fs
|
9
|
+
CHECK_FILE = FuseFS::Fuse::Root::CHECK_FILE[1..-1].to_sym
|
10
|
+
|
11
|
+
attr_reader :api
|
12
|
+
|
13
|
+
def initialize(api, opts)
|
14
|
+
@api = api
|
15
|
+
|
16
|
+
@mutex = Mutex.new
|
17
|
+
|
18
|
+
@path_cache = Cache.new(self)
|
19
|
+
@context = Context.new
|
20
|
+
@context.opts = opts
|
21
|
+
@context.url = opts[:device]
|
22
|
+
@context.mountpoint = ::File.realpath(opts[:mountpoint])
|
23
|
+
@context.cache = @path_cache
|
24
|
+
@context[:fs] = self
|
25
|
+
|
26
|
+
@root = Factory.create(@context, nil, Components::Root)
|
27
|
+
# @root.context = @context.clone
|
28
|
+
# @root.context[:root] = @root
|
29
|
+
@root.setup
|
30
|
+
|
31
|
+
Thread.abort_on_exception = true
|
32
|
+
@cleaner = Cleaner.new(self, @root)
|
33
|
+
@cleaner.start
|
34
|
+
@path_cache.start
|
35
|
+
end
|
36
|
+
|
37
|
+
def contents(path)
|
38
|
+
puts "contents"
|
39
|
+
p path
|
40
|
+
|
41
|
+
guard { find_component(path).contents }
|
42
|
+
end
|
43
|
+
|
44
|
+
def directory?(path)
|
45
|
+
puts "directory?"
|
46
|
+
p path
|
47
|
+
|
48
|
+
guard { find_component(path).directory? }
|
49
|
+
end
|
50
|
+
|
51
|
+
def file?(path)
|
52
|
+
puts "file?"
|
53
|
+
p path
|
54
|
+
|
55
|
+
guard {find_component(path).file? }
|
56
|
+
end
|
57
|
+
|
58
|
+
def can_read?(path)
|
59
|
+
puts "can_read?"
|
60
|
+
p path
|
61
|
+
|
62
|
+
guard { find_component(path).readable? }
|
63
|
+
end
|
64
|
+
|
65
|
+
def can_write?(path)
|
66
|
+
puts "can_write?"
|
67
|
+
p path
|
68
|
+
|
69
|
+
guard { find_component(path).writable? }
|
70
|
+
end
|
71
|
+
|
72
|
+
def executable?(path)
|
73
|
+
puts "executable?"
|
74
|
+
p path
|
75
|
+
|
76
|
+
guard { find_component(path).executable? }
|
77
|
+
end
|
78
|
+
|
79
|
+
def times(path)
|
80
|
+
puts "times"
|
81
|
+
p path
|
82
|
+
|
83
|
+
guard { find_component(path).times }
|
84
|
+
end
|
85
|
+
|
86
|
+
def size(path)
|
87
|
+
puts "size"
|
88
|
+
p path
|
89
|
+
|
90
|
+
guard { find_component(path).size }
|
91
|
+
end
|
92
|
+
|
93
|
+
def read_file(path)
|
94
|
+
puts "read_file"
|
95
|
+
p path
|
96
|
+
|
97
|
+
guard { find_component(path).read }
|
98
|
+
end
|
99
|
+
|
100
|
+
def write_to(path, str)
|
101
|
+
puts "write_to"
|
102
|
+
p path
|
103
|
+
|
104
|
+
guard { find_component(path).write(str) }
|
105
|
+
end
|
106
|
+
|
107
|
+
def raw_open(path, *args)
|
108
|
+
puts "raw_open"
|
109
|
+
p path
|
110
|
+
|
111
|
+
guard { find_component(path).raw_open(path, *args) }
|
112
|
+
end
|
113
|
+
|
114
|
+
def raw_read(path, *args)
|
115
|
+
puts "raw_read"
|
116
|
+
p path
|
117
|
+
|
118
|
+
guard { find_component(path).raw_read(path, *args) }
|
119
|
+
end
|
120
|
+
|
121
|
+
def raw_write(path, *args)
|
122
|
+
puts "raw_write"
|
123
|
+
p path
|
124
|
+
|
125
|
+
guard { find_component(path).raw_write(path, *args) }
|
126
|
+
end
|
127
|
+
|
128
|
+
def raw_sync(path, *args)
|
129
|
+
puts "raw_sync"
|
130
|
+
p path
|
131
|
+
|
132
|
+
guard { find_component(path).raw_sync(path, *args) }
|
133
|
+
end
|
134
|
+
|
135
|
+
def raw_truncate(path, *args)
|
136
|
+
puts "raw_truncate"
|
137
|
+
p path
|
138
|
+
|
139
|
+
guard { find_component(path).raw_truncate(path, *args) }
|
140
|
+
end
|
141
|
+
|
142
|
+
def raw_close(path, *args)
|
143
|
+
puts "raw_close"
|
144
|
+
p path
|
145
|
+
|
146
|
+
guard { find_component(path).raw_close(path, *args) }
|
147
|
+
end
|
148
|
+
|
149
|
+
def unmounted
|
150
|
+
puts "unmounted"
|
151
|
+
|
152
|
+
@cleaner.stop
|
153
|
+
@path_cache.stop
|
154
|
+
end
|
155
|
+
|
156
|
+
def synchronize
|
157
|
+
@mutex.synchronize { yield }
|
158
|
+
end
|
159
|
+
|
160
|
+
protected
|
161
|
+
def find_component(path)
|
162
|
+
@path_cache.get(path) do
|
163
|
+
t = Time.now
|
164
|
+
|
165
|
+
names = path.split('/').map { |v| v.to_sym }[1..-1]
|
166
|
+
tmp = @root
|
167
|
+
|
168
|
+
next(tmp) unless names
|
169
|
+
|
170
|
+
names.each do |n|
|
171
|
+
tmp = tmp.find(n)
|
172
|
+
|
173
|
+
if tmp.nil?
|
174
|
+
raise Errno::ENOENT, "'#{path}' not found"
|
175
|
+
|
176
|
+
else
|
177
|
+
tmp.atime = t
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
next(tmp)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def guard
|
186
|
+
synchronize { yield }
|
187
|
+
|
188
|
+
rescue => e
|
189
|
+
raise e if e.is_a?(::SystemCallError)
|
190
|
+
|
191
|
+
warn "Exception #{e.class}"
|
192
|
+
warn e.backtrace.join("\n")
|
193
|
+
warn e.message
|
194
|
+
|
195
|
+
raise Errno::EIO, e.message
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module HaveAPI::Fs
|
2
|
+
# Included this module to a {Component} to provide help files. All components
|
3
|
+
# based on {Components::Directory} already have this module included.
|
4
|
+
module Help
|
5
|
+
# When searching for a help file, all directories in this list are checked.
|
6
|
+
# Add paths to this list for help files of third-party components.
|
7
|
+
SEARCH_PATH = [
|
8
|
+
::File.realpath(::File.join(
|
9
|
+
::File.dirname(__FILE__),
|
10
|
+
'..', '..', '..',
|
11
|
+
'templates',
|
12
|
+
'help',
|
13
|
+
))
|
14
|
+
]
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
# Specify a name of a help file for this component. By default, the class
|
18
|
+
# name is used.
|
19
|
+
# @param [Symbol, nil] name
|
20
|
+
# @return [Symbol] if name is nil
|
21
|
+
def help_file(name = nil)
|
22
|
+
if name
|
23
|
+
@help_file = name
|
24
|
+
|
25
|
+
else
|
26
|
+
@help_file
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module InstanceMethods
|
32
|
+
protected
|
33
|
+
# List of help files in all formats as symbols.
|
34
|
+
# @return [Array<Symbol>]
|
35
|
+
def help_files
|
36
|
+
%i(html txt md man).map { |v| :"help.#{v}" }
|
37
|
+
end
|
38
|
+
|
39
|
+
# List of help files in all formats as strings.
|
40
|
+
# @return [Array<String>]
|
41
|
+
def help_contents
|
42
|
+
help_files.map(&:to_s)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Check if `name` is a help file.
|
46
|
+
# @param [Symbol] name
|
47
|
+
def help_file?(name)
|
48
|
+
help_files.include?(name)
|
49
|
+
end
|
50
|
+
|
51
|
+
# @return [Components::HelpFile] the recipe for a subclass, as
|
52
|
+
# {Component#new_child}
|
53
|
+
def help_file(name)
|
54
|
+
format = name.to_s.split('.').last.to_sym
|
55
|
+
|
56
|
+
case format
|
57
|
+
when :html
|
58
|
+
[Components::HtmlHelpFile, self, format]
|
59
|
+
|
60
|
+
when :txt, :md
|
61
|
+
[Components::MdHelpFile, self, :md]
|
62
|
+
|
63
|
+
when :man
|
64
|
+
[Components::GroffHelpFile, self, :md]
|
65
|
+
|
66
|
+
else
|
67
|
+
return nil
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class << self
|
73
|
+
def included(klass)
|
74
|
+
klass.send(:extend, ClassMethods)
|
75
|
+
klass.send(:include, InstanceMethods)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Search for `name` in paths defined in {SEARCH_PATH}.
|
79
|
+
# @return [String]
|
80
|
+
def find!(name)
|
81
|
+
SEARCH_PATH.each do |s|
|
82
|
+
path = ::File.join(s, name)
|
83
|
+
|
84
|
+
return path if ::File.exists?(path)
|
85
|
+
end
|
86
|
+
|
87
|
+
raise Errno::ENOENT, "help file '#{name}' not found"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module HaveAPI::Fs
|
5
|
+
# A list of accepted mount options
|
6
|
+
OPTIONS = %i(api version auth_method user password token nodaemonize log
|
7
|
+
index_limit)
|
8
|
+
USAGE = <<END
|
9
|
+
version=VERSION API version to use
|
10
|
+
auth_method=METHOD Authentication method (basic, token, noauth)
|
11
|
+
user Username
|
12
|
+
password Password
|
13
|
+
token Authentication token
|
14
|
+
nodaemonize Stay in the foreground
|
15
|
+
log Enable logging while daemonized
|
16
|
+
index_limit=LIMIT Limit number of objects in resource directory
|
17
|
+
END
|
18
|
+
|
19
|
+
|
20
|
+
# Every authentication provider must register using this method.
|
21
|
+
# @param [Symbol] name
|
22
|
+
# @param [Class] klass
|
23
|
+
def self.register_auth(name, klass)
|
24
|
+
@auth_methods ||= {}
|
25
|
+
@auth_methods[name] = klass
|
26
|
+
end
|
27
|
+
|
28
|
+
# Return authentication method based on mount options or return the default
|
29
|
+
# one.
|
30
|
+
# @param [Hash] opts mount options
|
31
|
+
# @param [Symbol] default name of the default authentication method
|
32
|
+
def self.auth_method(opts, default)
|
33
|
+
return @auth_methods[opts[:auth_method].to_sym] if opts[:auth_method]
|
34
|
+
|
35
|
+
@auth_methods.each_value do |m|
|
36
|
+
return m if m.use?(opts)
|
37
|
+
end
|
38
|
+
|
39
|
+
default ? @auth_methods[default] : @auth_methods.values.first
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [Hash] if the config exists
|
43
|
+
# @return [nil] if the config does not exist
|
44
|
+
def self.read_config
|
45
|
+
config_path = "#{Dir.home}/.haveapi-client.yml"
|
46
|
+
|
47
|
+
if File.exists?(config_path)
|
48
|
+
YAML.load_file(config_path)
|
49
|
+
|
50
|
+
else
|
51
|
+
nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Return configuration of a particular server from the config hash.
|
56
|
+
# @param [String] url URL of the API server
|
57
|
+
def self.server_config(url)
|
58
|
+
cfg = read_config
|
59
|
+
return nil if cfg.nil? || cfg[:servers].nil?
|
60
|
+
|
61
|
+
cfg[:servers].detect { |s| s[:url] == url }
|
62
|
+
end
|
63
|
+
|
64
|
+
# Perform a double-fork to make the process independent. Stdout and stderr
|
65
|
+
# are redirected either to a log file or to /dev/null.
|
66
|
+
#
|
67
|
+
# @param [Hash] opts mount options
|
68
|
+
def self.daemonize(opts)
|
69
|
+
home = ::File.join(Dir.home, '.haveapi-fs', URI(opts[:device]).host)
|
70
|
+
FileUtils.mkpath(home)
|
71
|
+
|
72
|
+
pid = Process.fork
|
73
|
+
|
74
|
+
if pid
|
75
|
+
exit # Parent 1
|
76
|
+
|
77
|
+
else
|
78
|
+
pid = Process.fork
|
79
|
+
exit if pid # Parent 2
|
80
|
+
end
|
81
|
+
|
82
|
+
# Only the child gets here
|
83
|
+
STDIN.close
|
84
|
+
|
85
|
+
f = File.open(
|
86
|
+
opts[:log] ? File.join(home, 'haveapi-fs.log') : '/dev/null',
|
87
|
+
'w'
|
88
|
+
)
|
89
|
+
|
90
|
+
STDOUT.reopen(f)
|
91
|
+
STDERR.reopen(f)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Create and setup an instance of HaveAPI::Client::Client based on the mount
|
95
|
+
# options, calls self.daemonize if not configured otherwise.
|
96
|
+
#
|
97
|
+
# @param [Hash] opts mount options
|
98
|
+
# @return [HaveAPI::Client::Client]
|
99
|
+
def self.client(opts)
|
100
|
+
cfg = server_config(opts[:device])
|
101
|
+
client = HaveAPI::Client::Client.new(
|
102
|
+
opts[:device],
|
103
|
+
opts[:version],
|
104
|
+
identity: 'haveapi-fs',
|
105
|
+
)
|
106
|
+
|
107
|
+
auth_klass = auth_method(opts, cfg && cfg[:last_auth])
|
108
|
+
|
109
|
+
auth = auth_klass.new(
|
110
|
+
(cfg && cfg[:auth][auth_klass.method_name]) || {},
|
111
|
+
opts,
|
112
|
+
)
|
113
|
+
auth.validate
|
114
|
+
auth.authenticate(client)
|
115
|
+
|
116
|
+
# Fetch API description, must be done especially after authentication
|
117
|
+
client.setup
|
118
|
+
|
119
|
+
# Verify that authentication works
|
120
|
+
auth.check(client)
|
121
|
+
|
122
|
+
daemonize(opts) unless opts[:nodaemonize]
|
123
|
+
client
|
124
|
+
end
|
125
|
+
|
126
|
+
# Calls FuseFS.main with an instance of {HaveAPI::Fs::Fs}.
|
127
|
+
def self.main(options = OPTIONS, usage = USAGE)
|
128
|
+
FuseFS.main(ARGV, OPTIONS, USAGE, 'api_url') do |opts|
|
129
|
+
fail "provide argument 'api_url'" unless opts[:device]
|
130
|
+
|
131
|
+
HaveAPI::Fs.new(client(opts), opts)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module HaveAPI::Fs
|
2
|
+
# The purpose of this class is to handle commands received via the
|
3
|
+
# {Components::RemoteControlFile}.
|
4
|
+
class RemoteControl
|
5
|
+
# Call method #exec of a component at `path`.
|
6
|
+
#
|
7
|
+
# @param [Context] context
|
8
|
+
# @param [String] path
|
9
|
+
def self.execute(context, path)
|
10
|
+
c = context.fs.send(:find_component, path)
|
11
|
+
|
12
|
+
ret = c.exec
|
13
|
+
|
14
|
+
case ret
|
15
|
+
when true
|
16
|
+
{status: true}
|
17
|
+
|
18
|
+
when HaveAPI::Client::Response
|
19
|
+
{status: ret.ok?, message: ret.message, errors: ret.errors}
|
20
|
+
|
21
|
+
when HaveAPI::Client::ValidationError
|
22
|
+
{status: false, message: ret.message, errors: ret.errors}
|
23
|
+
|
24
|
+
else
|
25
|
+
{status: false, message: 'unknown response'}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module HaveAPI::Fs
|
4
|
+
# Base class for classes that perform some regular work in a separate thread.
|
5
|
+
class Worker
|
6
|
+
attr_reader :runs
|
7
|
+
|
8
|
+
# @param [HaveAPI::Fs::Fs] fs
|
9
|
+
def initialize(fs)
|
10
|
+
@fs = fs
|
11
|
+
@run = true
|
12
|
+
@pipe_r, @pipe_w = IO.pipe
|
13
|
+
@runs = 0
|
14
|
+
@mutex = Mutex.new
|
15
|
+
end
|
16
|
+
|
17
|
+
# Start the work thread.
|
18
|
+
def start
|
19
|
+
@thread = Thread.new do
|
20
|
+
@mutex.synchronize { @next_time = Time.now + start_delay }
|
21
|
+
wait(start_delay)
|
22
|
+
|
23
|
+
while @run do
|
24
|
+
@fs.synchronize { work }
|
25
|
+
|
26
|
+
@runs += 1
|
27
|
+
@mutex.synchronize do
|
28
|
+
@last_time = Time.now
|
29
|
+
@next_time = @last_time + work_period
|
30
|
+
end
|
31
|
+
|
32
|
+
wait(work_period)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Stop and join the work thread.
|
38
|
+
def stop
|
39
|
+
@run = false
|
40
|
+
@pipe_w.write('CLOSE')
|
41
|
+
@thread.join
|
42
|
+
|
43
|
+
@pipe_r.close
|
44
|
+
@pipe_w.close
|
45
|
+
end
|
46
|
+
|
47
|
+
# The time when the work method was last run.
|
48
|
+
def last_time
|
49
|
+
@mutex.synchronize { @last_time }
|
50
|
+
end
|
51
|
+
|
52
|
+
# The time when the work method will be run next.
|
53
|
+
def next_time
|
54
|
+
@mutex.synchronize { @next_time }
|
55
|
+
end
|
56
|
+
|
57
|
+
protected
|
58
|
+
def wait(n)
|
59
|
+
IO.select([@pipe_r], [], [], n)
|
60
|
+
end
|
61
|
+
|
62
|
+
# @return [Integer] number of seconds to wait before the first work
|
63
|
+
def start_delay
|
64
|
+
raise NotImplementedError
|
65
|
+
end
|
66
|
+
|
67
|
+
# @return [Integer] number of seconds to wait between working
|
68
|
+
def work_period
|
69
|
+
raise NotImplementedError
|
70
|
+
end
|
71
|
+
|
72
|
+
# This method is regularly called to perform the work.
|
73
|
+
def work
|
74
|
+
raise NotImplementedError
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/lib/haveapi/fs.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
module HaveAPI
|
2
|
+
module Fs
|
3
|
+
module Auth ; end
|
4
|
+
|
5
|
+
def self.new(*args)
|
6
|
+
HaveAPI::Fs::Fs.new(*args)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
require_relative '../core_ext/string'
|
12
|
+
require_relative 'fs/main'
|
13
|
+
require_relative 'fs/fs'
|
14
|
+
require_relative 'fs/context'
|
15
|
+
require_relative 'fs/worker'
|
16
|
+
require_relative 'fs/cleaner'
|
17
|
+
require_relative 'fs/cache'
|
18
|
+
require_relative 'fs/component'
|
19
|
+
require_relative 'fs/factory'
|
20
|
+
require_relative 'fs/help'
|
21
|
+
require_relative 'fs/remote_control'
|
22
|
+
require_relative 'fs/version'
|
23
|
+
require_relative 'fs/auth/base'
|
24
|
+
require_relative 'fs/auth/basic'
|
25
|
+
require_relative 'fs/auth/token'
|
26
|
+
require_relative 'fs/auth/noauth'
|
27
|
+
require_relative 'fs/components/directory'
|
28
|
+
require_relative 'fs/components/file'
|
29
|
+
require_relative 'fs/components/executable'
|
30
|
+
require_relative 'fs/components/remote_control_file'
|
31
|
+
require_relative 'fs/components/directory_reset'
|
32
|
+
require_relative 'fs/components/root'
|
33
|
+
require_relative 'fs/components/resource_dir'
|
34
|
+
require_relative 'fs/components/index_filter'
|
35
|
+
require_relative 'fs/components/resource_instance_dir'
|
36
|
+
require_relative 'fs/components/save_instance'
|
37
|
+
require_relative 'fs/components/resource_action_dir'
|
38
|
+
require_relative 'fs/components/action_dir'
|
39
|
+
require_relative 'fs/components/create_action_dir'
|
40
|
+
require_relative 'fs/components/delete_action_dir'
|
41
|
+
require_relative 'fs/components/update_action_dir'
|
42
|
+
require_relative 'fs/components/action_input'
|
43
|
+
require_relative 'fs/components/action_output'
|
44
|
+
require_relative 'fs/components/list_item'
|
45
|
+
require_relative 'fs/components/action_status'
|
46
|
+
require_relative 'fs/components/action_message'
|
47
|
+
require_relative 'fs/components/action_errors'
|
48
|
+
require_relative 'fs/components/action_exec'
|
49
|
+
require_relative 'fs/components/action_exec_edit'
|
50
|
+
require_relative 'fs/components/parameter'
|
51
|
+
require_relative 'fs/components/resource_id'
|
52
|
+
require_relative 'fs/components/help_file'
|
53
|
+
require_relative 'fs/components/html_help_file'
|
54
|
+
require_relative 'fs/components/md_help_file'
|
55
|
+
require_relative 'fs/components/groff_help_file'
|
56
|
+
require_relative 'fs/components/instance_create'
|
57
|
+
require_relative 'fs/components/instance_edit'
|
58
|
+
require_relative 'fs/components/unsaved_list'
|
59
|
+
require_relative 'fs/components/component_list'
|
60
|
+
require_relative 'fs/components/meta_dir'
|
61
|
+
require_relative 'fs/components/meta_file'
|
62
|
+
require_relative 'fs/components/info_files'
|
63
|
+
require_relative 'fs/components/cache_stats'
|
64
|
+
require_relative 'fs/components/pry'
|
65
|
+
require_relative 'fs/components/rfuse_check'
|
@@ -0,0 +1,33 @@
|
|
1
|
+
<p><%= @c.action.description %></p>
|
2
|
+
<h2>Contents</h2>
|
3
|
+
<p>
|
4
|
+
This directory is used to prepare input parameters, execute the action and retrieve
|
5
|
+
output parameters.
|
6
|
+
</p>
|
7
|
+
<dl>
|
8
|
+
<dt><a href="input/help.html">input/</a></dt>
|
9
|
+
<dd>Stage area for input parameters.</dd>
|
10
|
+
|
11
|
+
<dt><a href="output/help.html">output/</a></dt>
|
12
|
+
<dd>Contains output parameters after the action was executed.</dd>
|
13
|
+
|
14
|
+
<dt>exec</dt>
|
15
|
+
<dd>Write <code>1</code> to this file to execute the action.</dd>
|
16
|
+
|
17
|
+
<dt>exec.yml</dt>
|
18
|
+
<dd>Editable YAML file with input parameters. The action is executed when the file
|
19
|
+
is saved and closed.</dd>
|
20
|
+
|
21
|
+
<dt>status</dt>
|
22
|
+
<dd>Read action status. <code>1</code> if successful, <code>0</code> if not, empty
|
23
|
+
if not yet run.</dd>
|
24
|
+
|
25
|
+
<dt>message</dt>
|
26
|
+
<dd>Error message returned from the API.</dd>
|
27
|
+
|
28
|
+
<dt><a href="errors/help.html">errors/</a></dt>
|
29
|
+
<dd>List of parameter errors.</dd>
|
30
|
+
|
31
|
+
<dt>reset</dt>
|
32
|
+
<dd>Reset input parameters.</dd>
|
33
|
+
</dl>
|