nightcrawler_swift 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +11 -0
  3. data/Gemfile.lock +1 -1
  4. data/README.md +40 -25
  5. data/bin/nswift +8 -0
  6. data/lib/nightcrawler_swift/cli.rb +208 -0
  7. data/lib/nightcrawler_swift/command.rb +12 -2
  8. data/lib/nightcrawler_swift/{delete.rb → commands/delete.rb} +1 -1
  9. data/lib/nightcrawler_swift/{download.rb → commands/download.rb} +1 -1
  10. data/lib/nightcrawler_swift/{list.rb → commands/list.rb} +0 -0
  11. data/lib/nightcrawler_swift/{sync.rb → commands/sync.rb} +2 -2
  12. data/lib/nightcrawler_swift/commands/upload.rb +25 -0
  13. data/lib/nightcrawler_swift/connection.rb +44 -23
  14. data/lib/nightcrawler_swift/exceptions.rb +1 -0
  15. data/lib/nightcrawler_swift/tasks/asset_sync.rake +7 -1
  16. data/lib/nightcrawler_swift/version.rb +1 -1
  17. data/lib/nightcrawler_swift.rb +26 -9
  18. data/spec/fixtures/auth_success.json +6 -6
  19. data/spec/lib/nightcrawler_swift/cli_spec.rb +293 -0
  20. data/spec/lib/nightcrawler_swift/command_spec.rb +55 -14
  21. data/spec/lib/nightcrawler_swift/{delete_spec.rb → commands/delete_spec.rb} +15 -8
  22. data/spec/lib/nightcrawler_swift/{download_spec.rb → commands/download_spec.rb} +16 -7
  23. data/spec/lib/nightcrawler_swift/{list_spec.rb → commands/list_spec.rb} +12 -8
  24. data/spec/lib/nightcrawler_swift/{sync_spec.rb → commands/sync_spec.rb} +10 -1
  25. data/spec/lib/nightcrawler_swift/{upload_spec.rb → commands/upload_spec.rb} +23 -8
  26. data/spec/lib/nightcrawler_swift/connection_spec.rb +32 -20
  27. data/spec/lib/nightcrawler_swift/tasks/asset_sync_spec.rb +19 -3
  28. data/spec/lib/nightcrawler_swift_spec.rb +46 -15
  29. metadata +23 -18
  30. data/lib/nightcrawler_swift/upload.rb +0 -17
@@ -1,26 +1,13 @@
1
1
  module NightcrawlerSwift
2
2
  class Connection
3
- attr_accessor :opts, :auth_response, :token_id, :expires_at, :admin_url, :upload_url, :public_url
4
-
5
- # Hash with: bucket, tenant_name, username, password, auth_url
6
- #
7
- def initialize opts = {}
8
- @opts = OpenStruct.new opts
9
- raise NightcrawlerSwift::Exceptions::ConfigurationError.new "max_age should be an Integer" if @opts.max_age and not @opts.max_age.is_a? Numeric
10
- end
3
+ attr_accessor :auth_response
4
+ attr_reader :token_id, :expires_at, :catalog, :admin_url, :upload_url, :public_url
11
5
 
12
6
  def connect!
13
- auth_response = authenticate!
14
-
15
- @token_id = auth_response.access["token"]["id"]
16
- @expires_at = auth_response.access["token"]["expires"]
17
- @expires_at = DateTime.parse(@expires_at).to_time
7
+ authenticate!
8
+ configure
18
9
 
19
- @admin_url = auth_response.access["serviceCatalog"].first["endpoints"].first["adminURL"]
20
- @upload_url = "#{@admin_url}/#{opts.bucket}"
21
- @public_url = auth_response.access["serviceCatalog"].first["endpoints"].first["publicURL"]
22
-
23
- NightcrawlerSwift.logger.info "Connected, token_id: #{@token_id}"
10
+ NightcrawlerSwift.logger.debug "[NightcrawlerSwift] Connected, token_id: #{token_id}"
24
11
  self
25
12
  end
26
13
 
@@ -28,7 +15,17 @@ module NightcrawlerSwift
28
15
  !self.token_id.nil? and self.expires_at > Time.now
29
16
  end
30
17
 
18
+ def configure
19
+ select_token
20
+ select_catalog
21
+ select_endpoints
22
+ configure_urls
23
+ end
24
+
31
25
  private
26
+ def opts
27
+ NightcrawlerSwift.options
28
+ end
32
29
 
33
30
  def authenticate!
34
31
  auth_options = {
@@ -36,15 +33,39 @@ module NightcrawlerSwift
36
33
  passwordCredentials: {username: opts.username, password: opts.password}
37
34
  }
38
35
 
39
- response = RestClient.post(
40
- opts.auth_url, { auth: auth_options }.to_json,
41
- content_type: :json,
42
- accept: :json,
36
+ resource = RestClient::Resource.new(
37
+ opts.auth_url,
38
+ verify_ssl: NightcrawlerSwift.options.verify_ssl,
39
+ timeout: NightcrawlerSwift.options.timeout
43
40
  )
44
41
 
45
- OpenStruct.new(JSON.parse(response.body))
42
+ response = resource.post({ auth: auth_options }.to_json, content_type: :json, accept: :json)
43
+
44
+ @auth_response = OpenStruct.new(JSON.parse(response.body))
46
45
  rescue StandardError => e
47
46
  raise Exceptions::ConnectionError.new(e)
48
47
  end
48
+
49
+ def select_token
50
+ @token_id = auth_response.access["token"]["id"]
51
+ @expires_at = auth_response.access["token"]["expires"]
52
+ @expires_at = DateTime.parse(@expires_at).to_time
53
+ end
54
+
55
+ def select_catalog
56
+ catalogs = auth_response.access["serviceCatalog"]
57
+ @catalog = catalogs.find {|catalog| catalog["type"] == "object-store"}
58
+ end
59
+
60
+ def select_endpoints
61
+ raise Exceptions::ConfigurationError.new "No catalog of type 'object-store' found" if catalog.nil?
62
+ @endpoints = catalog["endpoints"].first
63
+ end
64
+
65
+ def configure_urls
66
+ @admin_url = @endpoints["adminURL"]
67
+ @upload_url = "#{@admin_url}/#{opts.bucket}"
68
+ @public_url = @endpoints["publicURL"]
69
+ end
49
70
  end
50
71
  end
@@ -11,6 +11,7 @@ module NightcrawlerSwift
11
11
  end
12
12
 
13
13
  class ConnectionError < BaseError; end
14
+ class ValidationError < ConnectionError; end
14
15
  class NotFoundError < BaseError; end
15
16
  class ConfigurationError < StandardError; end
16
17
  end
@@ -4,7 +4,7 @@ namespace :nightcrawler_swift do
4
4
  namespace :rails do
5
5
 
6
6
  desc "Synchronizes the public directory with OpenStack Swift"
7
- task asset_sync: ["assets:precompile", "environment"] do
7
+ task sync: ["environment"] do
8
8
  begin
9
9
  NightcrawlerSwift.sync File.join(Rails.root, "public")
10
10
  rescue => e
@@ -13,5 +13,11 @@ namespace :nightcrawler_swift do
13
13
  end
14
14
  end
15
15
 
16
+ desc "Run 'assets:precompile' and synchronizes the public directory with OpenStack Swift"
17
+ task :asset_sync do
18
+ Rake::Task["assets:precompile"].invoke
19
+ Rake::Task["nightcrawler_swift:rails:sync"].invoke
20
+ end
21
+
16
22
  end
17
23
  end
@@ -1,3 +1,3 @@
1
1
  module NightcrawlerSwift
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -1,5 +1,6 @@
1
1
  require "date"
2
2
  require "logger"
3
+ require "digest"
3
4
  require "ostruct"
4
5
  require "multi_mime"
5
6
  require "rest_client"
@@ -7,29 +8,45 @@ require "nightcrawler_swift/version"
7
8
  require "nightcrawler_swift/exceptions"
8
9
  require "nightcrawler_swift/command"
9
10
  require "nightcrawler_swift/connection"
10
- require "nightcrawler_swift/upload"
11
- require "nightcrawler_swift/download"
12
- require "nightcrawler_swift/list"
13
- require "nightcrawler_swift/delete"
14
- require "nightcrawler_swift/sync"
11
+ require "nightcrawler_swift/commands/upload"
12
+ require "nightcrawler_swift/commands/download"
13
+ require "nightcrawler_swift/commands/list"
14
+ require "nightcrawler_swift/commands/delete"
15
+ require "nightcrawler_swift/commands/sync"
15
16
  require "nightcrawler_swift/railtie" if defined?(Rails)
16
17
 
17
18
  module NightcrawlerSwift
18
19
  class << self
19
20
 
20
21
  attr_accessor :logger
21
- attr_reader :connection
22
+ attr_reader :options, :connection
22
23
 
23
24
  def logger
24
- @logger || Logger.new(STDOUT)
25
+ @logger ||= Logger.new(STDOUT).tap {|l| l.level = Logger::INFO}
25
26
  end
26
27
 
28
+ # Hash with:
29
+ # - bucket
30
+ # - tenant_name
31
+ # - username
32
+ # - password
33
+ # - auth_url
34
+ #
35
+ # - max_age (optional, default: nil)
36
+ # - verify_ssl (optional, default: false)
37
+ # - timeout (in seconds. Optional, default: nil)
38
+ #
27
39
  def configure opts = {}
28
- @connection = Connection.new opts
40
+ @options = OpenStruct.new({verify_ssl: false}.merge(opts))
41
+
42
+ if @options.max_age and not @options.max_age.is_a?(Numeric)
43
+ raise Exceptions::ConfigurationError.new "max_age should be an Integer"
44
+ end
45
+
46
+ @connection = Connection.new
29
47
  end
30
48
 
31
49
  def sync dir_path
32
- connection.connect!
33
50
  Sync.new.execute(dir_path)
34
51
  end
35
52
 
@@ -7,9 +7,9 @@
7
7
  "type" : "object-store",
8
8
  "endpoints" : [
9
9
  {
10
- "publicURL" : "http://public.url.com",
11
- "internalURL" : "http://internal.url.com",
12
- "adminURL" : "http://api.url.com",
10
+ "publicURL" : "http://public-url-com",
11
+ "internalURL" : "http://internal-url-com",
12
+ "adminURL" : "http://api-url-com",
13
13
  "region" : "RegionOne",
14
14
  "id" : "1"
15
15
  }
@@ -21,9 +21,9 @@
21
21
  "type" : "identity",
22
22
  "endpoints" : [
23
23
  {
24
- "publicURL" : "http://auth.url.com:123/v2.0",
25
- "internalURL" : "https://auth.url.com:123/v2.0",
26
- "adminURL" : "https://auth.url.com:321/v2.0",
24
+ "publicURL" : "http://auth-url-com:123/v2.0",
25
+ "internalURL" : "https://auth-url-com:123/v2.0",
26
+ "adminURL" : "https://auth-url-com:321/v2.0",
27
27
  "region" : "RegionOne",
28
28
  "id" : "2"
29
29
  }
@@ -0,0 +1,293 @@
1
+ require "spec_helper"
2
+ require "nightcrawler_swift/cli"
3
+
4
+ describe NightcrawlerSwift::CLI do
5
+
6
+ let :config_dir do
7
+ File.expand_path(File.join(File.dirname(__FILE__), "../../fixtures"))
8
+ end
9
+
10
+ let :config_file do
11
+ File.join(config_dir, NightcrawlerSwift::CLI::CONFIG_FILE)
12
+ end
13
+
14
+ let :cache_file do
15
+ File.join(config_dir, NightcrawlerSwift::CLI::CACHE_FILE)
16
+ end
17
+
18
+ let :connection_success_json do
19
+ JSON.parse File.read(File.join(File.dirname(__FILE__), "../..", "fixtures/auth_success.json"))
20
+ end
21
+
22
+ let :opts do
23
+ {
24
+ "bucket" => "my-bucket-name",
25
+ "tenant_name" => "tenant_username1",
26
+ "username" => "username1",
27
+ "password" => "some-pass",
28
+ "auth_url" => "https://auth-url-com:123/v2.0/tokens"
29
+ }
30
+ end
31
+
32
+ subject do
33
+ NightcrawlerSwift::CLI.new argv.dup
34
+ end
35
+
36
+ before do
37
+ allow(subject).to receive(:user_home_dir).and_return(config_dir)
38
+ end
39
+
40
+ after do
41
+ File.delete(config_file) if File.exist?(config_file)
42
+ File.delete(cache_file) if File.exist?(cache_file)
43
+ end
44
+
45
+ shared_examples "CLI with default options" do
46
+ before do
47
+ File.open(config_file, "w") {|f| f.write(opts.to_json)}
48
+ allow(subject).to receive(:execute_command)
49
+ end
50
+
51
+ it "points to a config file in the user home dir" do
52
+ subject.run
53
+ path = File.join(config_dir, NightcrawlerSwift::CLI::CONFIG_FILE)
54
+ expect(subject.options.config_file).to eql File.expand_path(path)
55
+ end
56
+
57
+ it "points to a cache file in the uder home dir" do
58
+ subject.run
59
+ path = File.join(config_dir, NightcrawlerSwift::CLI::CACHE_FILE)
60
+ expect(subject.options.cache_file).to eql File.expand_path(path)
61
+ end
62
+ end
63
+
64
+ shared_examples "CLI with parsed parameters" do
65
+ context "--version or -v" do
66
+ it "prints the version number" do
67
+ subject.argv << "-v"
68
+ expect(subject).to receive(:log).with(NightcrawlerSwift::VERSION)
69
+ expect { subject.run }.to raise_error SystemExit
70
+
71
+ expect(subject).to receive(:log).with(NightcrawlerSwift::VERSION)
72
+ subject.argv = (argv << "--version")
73
+ expect { subject.run }.to raise_error SystemExit
74
+ end
75
+ end
76
+
77
+ context "--help or -h" do
78
+ before do
79
+ subject.send :configure_default_options
80
+ subject.send :configure_opt_parser
81
+
82
+ allow(subject).to receive(:configure_default_options)
83
+ allow(subject).to receive(:configure_opt_parser)
84
+ end
85
+
86
+ it "prints the help" do
87
+ subject.argv << "-h"
88
+ expect(subject).to receive(:log).with(subject.opt_parser.help)
89
+ expect { subject.run }.to raise_error SystemExit
90
+
91
+ expect(subject).to receive(:log).with(subject.opt_parser.help)
92
+ subject.argv = (argv << "--help")
93
+ expect { subject.run }.to raise_error SystemExit
94
+ end
95
+ end
96
+
97
+ context "--config or -c" do
98
+ let :config_file do
99
+ File.join(config_dir, "#{NightcrawlerSwift::CLI::CONFIG_FILE}-test")
100
+ end
101
+
102
+ before do
103
+ allow(subject).to receive(:execute_command)
104
+ allow(subject).to receive(:log)
105
+ File.open(config_file, "w") {|f| f.write("test")}
106
+ end
107
+
108
+ after do
109
+ File.delete(config_file) if File.exist?(config_file)
110
+ end
111
+
112
+ it "configures the config_file" do
113
+ subject.argv << "-c #{config_file}"
114
+ subject.run
115
+ expect(subject.options.config_file).to eql config_file
116
+
117
+ subject.argv = (argv << "--config=#{config_file}")
118
+ subject.run
119
+ expect(subject.options.config_file).to eql config_file
120
+ end
121
+ end
122
+ end
123
+
124
+ shared_examples "CLI that creates a sample config file" do
125
+ before do
126
+ allow(subject).to receive(:execute_command)
127
+ allow(subject).to receive(:log)
128
+ end
129
+
130
+ it "creates a sample config file" do
131
+ expect(File.exist?(config_file)).to eql false
132
+ expect { subject.run }.to raise_error SystemExit
133
+ expect(File.exist?(config_file)).to eql true
134
+ expect(File.read(config_file)).to eql subject.send(:sample_rcfile)
135
+ end
136
+
137
+ it "flags as not configured" do
138
+ expect { subject.run }.to raise_error SystemExit
139
+ expect(subject.options.configured).to eql false
140
+ end
141
+ end
142
+
143
+ shared_examples "CLI that uses the configured command" do
144
+ let :connection do
145
+ NightcrawlerSwift::Connection.new
146
+ end
147
+
148
+ before do
149
+ allow(subject).to receive(command_method)
150
+ allow(NightcrawlerSwift).to receive(:connection).and_return(connection)
151
+ File.open(config_file, "w") {|f| f.write(opts.to_json)}
152
+ end
153
+
154
+ it "configures NightcrawlerSwift" do
155
+ expect(NightcrawlerSwift).to receive(:configure).with opts
156
+ subject.run
157
+ end
158
+
159
+ context "when exist a previous valid authorization" do
160
+ it "reuses that" do
161
+ File.open(cache_file, "w") {|f| f.write(connection_success_json.to_json)}
162
+ expect(File.exist?(cache_file)).to eql true
163
+
164
+ expect(connection).to receive(:configure).and_call_original
165
+ subject.run
166
+ expect(connection.auth_response).to eql(OpenStruct.new(connection_success_json))
167
+ end
168
+ end
169
+
170
+ it "writes the auth_response into cache file" do
171
+ expect(File.exist?(cache_file)).to eql false
172
+ expect(connection).to receive(:auth_response).and_return(OpenStruct.new(connection_success_json))
173
+ subject.run
174
+ expect(File.exist?(cache_file)).to eql true
175
+ expect(File.read(cache_file)).to eql connection_success_json.to_json
176
+ end
177
+ end
178
+
179
+ describe "command list" do
180
+ let(:argv) { ["list"] }
181
+ let(:command) { NightcrawlerSwift::List.new }
182
+ let(:command_method) { :command_list }
183
+ let :result do
184
+ [{
185
+ "hash"=>"c9df50d4a29542f8b6d426a50c72b3de",
186
+ "last_modified"=>"2014-08-27T19:35:46.053560",
187
+ "bytes"=>4994,
188
+ "name"=>"assets/file.png",
189
+ "content_type"=>"image/png"
190
+ }]
191
+ end
192
+
193
+ it_behaves_like "CLI with default options"
194
+ it_behaves_like "CLI with parsed parameters"
195
+ it_behaves_like "CLI that creates a sample config file"
196
+ it_behaves_like "CLI that uses the configured command"
197
+
198
+ context "when executing the command" do
199
+ before do
200
+ File.open(config_file, "w") {|f| f.write(opts.to_json)}
201
+ end
202
+
203
+ it "lists all files in the bucket/container configured" do
204
+ expect(NightcrawlerSwift::List).to receive(:new).and_return(command)
205
+ expect(command).to receive(:execute).and_return(result)
206
+ expect(subject).to receive(:log).with(result.first["name"])
207
+ subject.run
208
+ end
209
+ end
210
+ end
211
+
212
+ describe "command download" do
213
+ let(:filepath) { "testfile.txt" }
214
+ let(:argv) { ["download", filepath] }
215
+ let(:command) { NightcrawlerSwift::Download.new }
216
+ let(:command_method) { :command_download }
217
+
218
+ it_behaves_like "CLI with default options"
219
+ it_behaves_like "CLI with parsed parameters"
220
+ it_behaves_like "CLI that creates a sample config file"
221
+ it_behaves_like "CLI that uses the configured command"
222
+
223
+ context "when executing the command" do
224
+ before do
225
+ File.open(config_file, "w") {|f| f.write(opts.to_json)}
226
+ end
227
+
228
+ it "downloads the file" do
229
+ expect(NightcrawlerSwift::Download).to receive(:new).and_return(command)
230
+ expect(command).to receive(:execute).with(filepath).and_return("test-content")
231
+ expect(subject).to receive(:log).with("test-content")
232
+ subject.run
233
+ end
234
+ end
235
+ end
236
+
237
+ describe "command upload" do
238
+ let(:swiftpath) { "testfile.txt" }
239
+ let(:realpath) { File.join(config_dir, swiftpath) }
240
+ let(:argv) { ["upload", realpath, swiftpath] }
241
+ let(:command) { NightcrawlerSwift::Upload.new }
242
+ let(:command_method) { :command_upload }
243
+
244
+ it_behaves_like "CLI with default options"
245
+ it_behaves_like "CLI with parsed parameters"
246
+ it_behaves_like "CLI that creates a sample config file"
247
+ it_behaves_like "CLI that uses the configured command"
248
+
249
+ context "when executing the command" do
250
+ before do
251
+ File.open(config_file, "w") {|f| f.write(opts.to_json)}
252
+ File.open(realpath, "w") {|f| f.write("test") }
253
+ end
254
+
255
+ after do
256
+ File.delete(realpath) if File.exist?(realpath)
257
+ end
258
+
259
+ it "uploads the file" do
260
+ expect(NightcrawlerSwift::Upload).to receive(:new).and_return(command)
261
+ expect(command).to receive(:execute).with(swiftpath, instance_of(File)).and_return(true)
262
+ expect(subject).to receive(:log).with("success")
263
+ subject.run
264
+ end
265
+ end
266
+ end
267
+
268
+ describe "command delete" do
269
+ let(:filepath) { "testfile.txt" }
270
+ let(:argv) { ["delete", filepath] }
271
+ let(:command) { NightcrawlerSwift::Delete.new }
272
+ let(:command_method) { :command_delete }
273
+
274
+ it_behaves_like "CLI with default options"
275
+ it_behaves_like "CLI with parsed parameters"
276
+ it_behaves_like "CLI that creates a sample config file"
277
+ it_behaves_like "CLI that uses the configured command"
278
+
279
+ context "when executing the command" do
280
+ before do
281
+ File.open(config_file, "w") {|f| f.write(opts.to_json)}
282
+ end
283
+
284
+ it "downloads the file" do
285
+ expect(NightcrawlerSwift::Delete).to receive(:new).and_return(command)
286
+ expect(command).to receive(:execute).with(filepath).and_return(true)
287
+ expect(subject).to receive(:log).with("success")
288
+ subject.run
289
+ end
290
+ end
291
+ end
292
+
293
+ end