nightcrawler_swift 0.3.0 → 0.4.0

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.
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