rest-ftp-daemon 0.6 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,3 +1,10 @@
1
1
  source "http://rubygems.org"
2
2
 
3
- gemspec
3
+ gem "sinatra"
4
+ gem "json"
5
+
6
+ group :development do
7
+ gem "shoulda", ">= 0"
8
+ gem "bundler", "~> 1.0"
9
+ gem "jeweler", "~> 2.0.1"
10
+ end
data/Gemfile.lock CHANGED
@@ -1,78 +1,81 @@
1
- PATH
2
- remote: .
3
- specs:
4
- rest-ftp-daemon (0.55)
5
- facter
6
- grape
7
- json
8
- settingslogic
9
- thin (~> 1.6)
10
-
11
1
  GEM
12
2
  remote: http://rubygems.org/
13
3
  specs:
14
- CFPropertyList (2.2.8)
15
- activesupport (4.1.6)
4
+ activesupport (4.1.4)
16
5
  i18n (~> 0.6, >= 0.6.9)
17
6
  json (~> 1.7, >= 1.7.7)
18
7
  minitest (~> 5.1)
19
8
  thread_safe (~> 0.1)
20
9
  tzinfo (~> 1.1)
21
- axiom-types (0.1.1)
22
- descendants_tracker (~> 0.0.4)
23
- ice_nine (~> 0.11.0)
24
- thread_safe (~> 0.3, >= 0.3.1)
10
+ addressable (2.3.6)
25
11
  builder (3.2.2)
26
- coercible (1.0.0)
27
- descendants_tracker (~> 0.0.1)
28
- daemons (1.1.9)
29
12
  descendants_tracker (0.0.4)
30
13
  thread_safe (~> 0.3, >= 0.3.1)
31
- equalizer (0.0.9)
32
- eventmachine (1.0.3)
33
- facter (2.2.0)
34
- CFPropertyList (~> 2.2.6)
35
- grape (0.9.0)
36
- activesupport
37
- builder
38
- hashie (>= 2.1.0)
39
- multi_json (>= 1.3.2)
40
- multi_xml (>= 0.5.2)
41
- rack (>= 1.3.0)
42
- rack-accept
43
- rack-mount
44
- virtus (>= 1.0.0)
45
- hashie (3.3.1)
14
+ faraday (0.9.0)
15
+ multipart-post (>= 1.2, < 3)
16
+ git (1.2.8)
17
+ github_api (0.12.0)
18
+ addressable (~> 2.3)
19
+ descendants_tracker (~> 0.0.4)
20
+ faraday (~> 0.8, < 0.10)
21
+ hashie (>= 3.2)
22
+ multi_json (>= 1.7.5, < 2.0)
23
+ nokogiri (~> 1.6.3)
24
+ oauth2
25
+ hashie (3.2.0)
26
+ highline (1.6.21)
46
27
  i18n (0.6.11)
47
- ice_nine (0.11.0)
28
+ jeweler (2.0.1)
29
+ builder
30
+ bundler (>= 1.0)
31
+ git (>= 1.2.5)
32
+ github_api
33
+ highline (>= 1.6.15)
34
+ nokogiri (>= 1.5.10)
35
+ rake
36
+ rdoc
48
37
  json (1.8.1)
49
- minitest (5.4.1)
38
+ jwt (1.0.0)
39
+ mini_portile (0.6.0)
40
+ minitest (5.4.0)
50
41
  multi_json (1.10.1)
51
42
  multi_xml (0.5.5)
43
+ multipart-post (2.0.0)
44
+ nokogiri (1.6.3.1)
45
+ mini_portile (= 0.6.0)
46
+ oauth2 (1.0.0)
47
+ faraday (>= 0.8, < 0.10)
48
+ jwt (~> 1.0)
49
+ multi_json (~> 1.3)
50
+ multi_xml (~> 0.5)
51
+ rack (~> 1.2)
52
52
  rack (1.5.2)
53
- rack-accept (0.4.5)
54
- rack (>= 0.4)
55
- rack-mount (0.8.3)
56
- rack (>= 1.0.0)
53
+ rack-protection (1.5.3)
54
+ rack
57
55
  rake (10.3.2)
58
- settingslogic (2.0.9)
59
- thin (1.6.2)
60
- daemons (>= 1.0.9)
61
- eventmachine (>= 1.0.0)
62
- rack (>= 1.0.0)
56
+ rdoc (4.1.1)
57
+ json (~> 1.4)
58
+ shoulda (3.5.0)
59
+ shoulda-context (~> 1.0, >= 1.0.1)
60
+ shoulda-matchers (>= 1.4.1, < 3.0)
61
+ shoulda-context (1.2.1)
62
+ shoulda-matchers (2.6.2)
63
+ activesupport (>= 3.0.0)
64
+ sinatra (1.4.5)
65
+ rack (~> 1.4)
66
+ rack-protection (~> 1.4)
67
+ tilt (~> 1.3, >= 1.3.4)
63
68
  thread_safe (0.3.4)
64
- tzinfo (1.2.2)
69
+ tilt (1.4.1)
70
+ tzinfo (1.2.1)
65
71
  thread_safe (~> 0.1)
66
- virtus (1.0.3)
67
- axiom-types (~> 0.1)
68
- coercible (~> 1.0)
69
- descendants_tracker (~> 0.0, >= 0.0.3)
70
- equalizer (~> 0.0, >= 0.0.9)
71
72
 
72
73
  PLATFORMS
73
74
  ruby
74
75
 
75
76
  DEPENDENCIES
76
- bundler (~> 1.6)
77
- rake
78
- rest-ftp-daemon!
77
+ bundler (~> 1.0)
78
+ jeweler (~> 2.0.1)
79
+ json
80
+ shoulda
81
+ sinatra
data/README.md CHANGED
@@ -1,46 +1,25 @@
1
1
  rest-ftp-daemon
2
2
  ====================================================================================
3
3
 
4
-
5
-
6
4
  This is a pretty simple FTP client daemon, controlled through a RESTfull API.
7
5
 
8
6
  As of today, its main features are :
9
7
 
10
- * Allow environment-specific configuration in a YAML file
11
- * Delegate a transfer job by ``POST```'ing a simple JSON structure
8
+ * Delegate a transfer job, ```PUT```'ing posting simple JSON structure
12
9
  * Spawn a dedicated thread to handle this job in its own context
13
- * Report transfer status, progress and errors for each job in realtime
10
+ * Report transfer status, progress and errors for each delegated job
14
11
  * Expose JSON status of workers on ```GET /jobs/``` for automated monitoring
15
- * Parralelize jobs as soon as they arrive
16
- * Handle job queues and priority as an attribute of the job
17
- * Allow dynamic evaluation of priorities, and change of any attribute until the job is picked
18
- * Provide RESTful notifications to the requesting client
19
- * Allow authentication in FTP target in a standard URI-format
20
- * Allow configuration-based path templates to abstract local mounts or remote FTPs (endpoint tokens)
21
-
22
-
23
- Expected features in a short-time range :
24
-
25
- * Allow change of priorities or other attributes after a job has been started
26
- * Offer a basic dashboard directly within the daemon HTTP interface
27
- * Periodically send an update-notification with transfer status and progress
28
- * Allow fallback file source when first file path is unavailable (failover)
29
- * Provide swagger-style API documentation
30
- * Authenticate API clients
31
-
32
12
 
33
13
 
34
14
  Installation
35
15
  ------------------------------------------------------------------------------------
36
16
 
37
- This project is available as a rubygem, requires on ruby >= 1.9.3 and rubygems installed.
17
+ This project is available as a rubygem, requires on ruby >= 1.9 and rubygems installed.
38
18
 
39
19
  Get and install the gem from rubygems.org:
40
20
 
41
21
  ```
42
- # apt-get install ruby1.9.3 ruby-dev rubygems gcc g++
43
- gem install rest-ftp-daemon --no-ri --no-rdoc
22
+ gem install rest-ftp-daemon
44
23
  ```
45
24
 
46
25
  Start the daemon:
@@ -49,117 +28,49 @@ Start the daemon:
49
28
  rest-ftp-daemon start
50
29
  ```
51
30
 
52
- Start the daemon on a specific port :
53
-
54
- ```
55
- rest-ftp-daemon -p4000 start
56
- ```
57
-
58
31
  Check that the daemon is running and giving status info
59
32
 
60
33
  ```
61
34
  http://localhost:3000/
62
35
  ```
63
36
 
64
- Configuration
65
- ------------------------------------------------------------------------------------
66
- Most of the configuration options live in a YAML configuration file, containing two main sections:
67
-
68
- * the ``defaults`` section should be left as-is and will be used is no other environment-specific value is provided.
69
- * the ``production`` section can receive personnalized settings according to your environment-specific setup and paths.
70
-
71
- Configuration priority is defined as follows (from most important to last resort):
72
-
73
- * command-line parameters
74
- * config file defaults section
75
- * config file environment section
76
- * application internal defaults
77
-
78
-
79
- As a starting point, ``rest-ftp-daemon.yml.sample`` is an exemple config file that can be copied into the expected location ``/etc/rest-ftp-daemon.yml``.
80
-
81
-
82
- Logging
83
- ------------------------------------------------------------------------------------
84
-
85
- The application will not log to any file by default, if not specified in its configuration.
86
- Otherwise separate logging paths can be provided for the Thin webserver, API related messages, and workers related messages. Providing and empty value will simply activate logging to ``STDOUT``.
37
+ For now, daemon logs to ```APP_LOGTO``` defined in ```lib/config.rb```
87
38
 
88
39
 
89
40
  Usage examples
90
41
  ------------------------------------------------------------------------------------
91
42
 
92
- * Start a job to transfer a file named "file.iso" to a local FTP server
43
+ Start a job to transfer a file named "file.ova" to a local FTP server
93
44
 
94
45
  ```
95
46
  curl -H "Content-Type: application/json" -X POST -D /dev/stdout -d \
96
- '{"source":"~/file.iso","target":"ftp://anonymous@localhost/incoming/dest2.iso"}' "http://localhost:3000/jobs"
47
+ '{"source":"~/file.ova","target":"ftp://anonymous@localhost/incoming/dest2.ova"}' "http://localhost:3000/jobs"
97
48
  ```
98
49
 
99
- Requesting notifications is achieved by passing a "notify" key in the request, with a callback URL.
100
- This URL will be called at some points, ``POST```'ing a generic JSON structure with progress information.
101
-
102
-
103
- * Start a job requesting notifications ``POST```'ed on "http://requestb.in/1321axg1"
50
+ Start a job to transfer a file named "file.dmg" to a local FTP server
104
51
 
105
52
  ```
106
53
  curl -H "Content-Type: application/json" -X POST -D /dev/stdout -d \
107
- '{"source":"~/file.dmg","target":"ftp://anonymous@localhost/incoming/dest4.dmg","notify":"http://requestb.in/1321axg1"}' "http://localhost:3000/jobs"
54
+ '{"source":"~/file.dmg","target":"ftp://anonymous@localhost/incoming/dest4.dmg"}' "http://localhost:3000/jobs"
108
55
  ```
109
56
 
110
- * Start a job with all the above plus a priority
57
+ Get status of a specific job based on its name
111
58
 
112
59
  ```
113
- curl -H "Content-Type: application/json" -X POST -D /dev/stdout -d \
114
- '{"source":"~/file.dmg","priority":"3", target":"ftp://anonymous@localhost/incoming/dest4.dmg","notify":"http://requestb.in/1321axg1"}' "http://localhost:3000/jobs"
60
+ curl -H "Content-Type: application/json" -X DELETE -D /dev/stdout "http://localhost:3000/jobs/bob-45320-1"
115
61
  ```
116
62
 
117
- * Start a job using endpoint tokens
118
-
119
- First define ``nas`` ans ``ftp1`` in the configuration file :
63
+ Delete a specific job based on its name
120
64
 
121
65
  ```
122
- defaults: &defaults
123
-
124
- development:
125
- <<: *defaults
126
-
127
- endpoints:
128
- nas: "~/"
129
- ftp1: "ftp://anonymous@localhost/incoming/"
130
- ```
131
-
132
- Thos tokens will be expanded when the job is ran :
133
-
134
- ```
135
- curl -H "Content-Type: application/json" -X POST -D /dev/stdout -d \
136
- '{"source":"~/file.dmg","priority":"3", target":"ftp://anonymous@localhost/incoming/dest4.dmg","notify":"http://requestb.in/1321axg1"}' "http://localhost:3000/jobs"
137
- ```
138
-
139
- NB: a special token [RANDOM] helps to generate a random filename when needed
140
-
141
- * Get status of a specific job based on its ID
142
-
143
- ```
144
- curl -H "Content-Type: application/json" -X GET -D /dev/stdout "http://localhost:3000/jobs/3"
145
- ```
146
-
147
-
148
- * Delete a specific job based on its ID
149
-
150
- ```
151
- curl -H "Content-Type: application/json" -X DELETE -D /dev/stdout "http://localhost:3000/jobs/3"
66
+ curl -H "Content-Type: application/json" -X DELETE -D /dev/stdout "http://localhost:3000/jobs/bob-45320-1"
152
67
  ```
153
68
 
154
69
 
155
70
  Getting status
156
71
  ------------------------------------------------------------------------------------
157
72
 
158
- * A global JSON status is provided on ``` GET /index.json ```
159
-
160
- * A nice dashboard gives a global view of the daemon, jobs in queue, and system status, exposed on ``` GET /```
161
-
162
- * The server exposes its jobs list on ``` GET /jobs ```
73
+ The server exposes jobs list on ``` GET /jobs ```
163
74
 
164
75
  ```
165
76
  http://localhost:3000/jobs
@@ -184,19 +95,18 @@ This query will return a job list :
184
95
  "transferred": 37100000
185
96
  },
186
97
  {
187
- source: "[nas]/file.dmg",
188
- target: "[ftp2]/dest4.dmg",
189
- notify: "http://requestb.in/1321axg1",
190
- updated_at: "2014-09-17 22:56:14 +0200",
191
- id: 2,
192
- started_at: "2014-09-17 22:56:01 +0200",
193
- status: "uploading",
194
- error: 0,
195
- debug_source: "/Users/bruno/file.dmg",
196
- debug_target: "#<URI::FTP:0x007ffa9289e650 URL:ftp://uuuuuuuu:yyyyyyyyy@ftp.xxxxxx.fr/subdir/dest4.dmg>",
197
- file_size: 32093208,
198
- file_progress: 5.6,
199
- file_sent: 1800000
98
+ "source": "~/file.ova",
99
+ "target": "ftp://anonymous@localhost/incoming/dest2.ova",
100
+ "worker_name": "bob-92439-2",
101
+ "created": "2014-08-01 16:53:12 +0200",
102
+ "id": 17,
103
+ "runtime": 13.8,
104
+ "status": "uploading",
105
+ "source_size": 1849036800,
106
+ "error": -1,
107
+ "errmsg": "uploading",
108
+ "progress": 36.1,
109
+ "transferred": 668300000
200
110
  }
201
111
  ]
202
112
  ```
data/Rakefile CHANGED
@@ -1,3 +1,51 @@
1
1
  # encoding: utf-8
2
- require "bundler/gem_tasks"
2
+
3
3
  require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://guides.rubygems.org/specification-reference/ for more options
17
+ gem.name = "rest-ftp-daemon"
18
+ gem.homepage = "http://github.com/bmedici/rest-ftp-daemon"
19
+ gem.license = "MIT"
20
+ gem.summary = "RESTful FTP client daemon"
21
+ gem.description = "This is a pretty simple FTP client daemon, controlled through a RESTfull API"
22
+ gem.email = "rest-ftp-daemon@bmconseil.com"
23
+ gem.authors = ["Bruno"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rake/testtask'
29
+ Rake::TestTask.new(:test) do |test|
30
+ test.libs << 'lib' << 'test'
31
+ test.pattern = 'test/**/test_*.rb'
32
+ test.verbose = true
33
+ end
34
+
35
+ desc "Code coverage detail"
36
+ task :simplecov do
37
+ ENV['COVERAGE'] = "true"
38
+ Rake::Task['test'].execute
39
+ end
40
+
41
+ task :default => :test
42
+
43
+ require 'rdoc/task'
44
+ Rake::RDocTask.new do |rdoc|
45
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
46
+
47
+ rdoc.rdoc_dir = 'rdoc'
48
+ rdoc.title = "rest-ftp-daemon #{version}"
49
+ rdoc.rdoc_files.include('README*')
50
+ rdoc.rdoc_files.include('lib/**/*.rb')
51
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.9.0
data/bin/rest-ftp-daemon CHANGED
@@ -1,42 +1,22 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  # Libs and init
4
- require "thin"
5
- DEVELOPMENT=ARGV.include?("development")
6
- HERE=File.expand_path(File.dirname(__FILE__) + '/../')
7
- #APP_NAME2="rest-ftp-daemon"
8
-
9
- # Include config file and build rack commandline
10
- require File.expand_path("#{HERE}/lib/rest-ftp-daemon/config.rb")
11
-
4
+ require 'thin'
5
+
6
+ # Initialize some local constants
7
+ APP_ROOT = File.dirname(__FILE__) + '/../'
8
+ APP_NAME = 'rest-ftp-daemon'
9
+ APP_VER = File.read "#{APP_ROOT}/VERSION"
10
+ APP_STARTED = Time.now
11
+ APP_DEFAULT_PORT = 3000
12
+ APP_LOGTO = "/tmp/#{APP_NAME}.log"
13
+
14
+ # Prepare thin
15
+ rackup_file = File.expand_path "#{APP_ROOT}/lib/config.ru"
12
16
  argv = ARGV
13
- argv << ["-p", Settings.port.to_s] unless ARGV.include?("-p") || Settings.port.nil?
14
- argv << ["-e", Settings.namespace] unless ARGV.include?("-e") || Settings.namespace.nil?
15
- argv << ["-l", Settings.logs.thin] unless ARGV.include?("-l") unless Settings.logs.thin.nil? unless Settings.logs.nil?
16
- argv << ["--daemonize"] if (Settings.daemonize==1 || Settings.daemonize.nil?) unless ARGV.include?("-d")
17
-
18
- # Rackup file
19
- argv << ["-R", File.expand_path("#{HERE}/config.ru")] unless ARGV.include?("-R")
20
-
21
- # Display compiled configuration
22
- puts
23
- puts "Daemon name \t #{Settings.name}"
24
- puts "Version \t #{Settings.version}"
25
- puts "Environment \t #{Settings.namespace}"
26
- puts "Config file \t #{Settings.source}"
27
- puts "Parameters \t #{argv.flatten}"
28
- puts Settings.to_hash.to_yaml( :Indent => 4, :UseHeader => true, :UseVersion => false )
29
-
30
- # Start Thin with this rackup configuration
31
- puts
32
- begin
33
- Thin::Runner.new(argv.flatten).run!
34
- rescue Thin::PidFileExist
35
- puts "EXITING: daemon was already running (Thin::PidFileExist)"
36
- rescue Thin::PidFileNotFound
37
- puts "EXITING: daemon was not running (Thin::PidFileNotFound)"
38
- else
39
- puts "PROCESS ENDING"
40
- end
41
-
17
+ argv << ["-R", rackup_file] unless ARGV.include?("-R")
18
+ argv << ["-p", APP_DEFAULT_PORT.to_s] unless ARGV.include?("-p")
19
+ argv << ["-e", "production"] unless ARGV.include?("-e")
20
+ argv << ["--stats", "/stats"] unless ARGV.include?("--stats")
42
21
 
22
+ Thin::Runner.new(argv.flatten).run!
data/lib/config.rb ADDED
@@ -0,0 +1,3 @@
1
+ # Transfer configuration
2
+ TRANSFER_CHUNK_SIZE = 100000
3
+ THREAD_SLEEP_BEFORE_DIE = 120
data/lib/config.ru ADDED
@@ -0,0 +1,11 @@
1
+ # Main libs
2
+ require 'sinatra'
3
+ require 'sinatra/base'
4
+ require 'net/ftp'
5
+ require 'json'
6
+
7
+ # My local libs
8
+ Dir[APP_ROOT+"/lib/*.rb"].each {|file| require File.expand_path file }
9
+
10
+ # Start application
11
+ run RestFtpDaemon
data/lib/errors.rb ADDED
@@ -0,0 +1,12 @@
1
+ ERR_OK = 0
2
+ ERR_BUSY = -1
3
+
4
+ ERR_REQ_SOURCE_MISSING = 11
5
+ ERR_REQ_TARGET_MISSING = 12
6
+ ERR_REQ_TARGET_SCHEME = 13
7
+
8
+ ERR_JOB_SOURCE_NOTFOUND = 21
9
+ ERR_JOB_TARGET_UNPARSEABLE = 22
10
+ ERR_JOB_PERMISSION = 24
11
+ ERR_JOB_TARGET_PRESENT = 25
12
+
@@ -0,0 +1,14 @@
1
+ class Thread
2
+
3
+ def job
4
+ # Basic fields
5
+ job = self[:job]
6
+
7
+ # Calculated fields
8
+ job[:id] = self[:id]
9
+ job[:runtime] = (Time.now - job[:created]).round(1)
10
+
11
+ return job
12
+ end
13
+
14
+ end