uricp 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +16 -0
- data/README.md +31 -0
- data/README.rdoc +23 -0
- data/Rakefile +66 -0
- data/bin/segment_upload +66 -0
- data/bin/uricp +103 -0
- data/features/auth_download.feature +106 -0
- data/features/cacheable_from_uri.feature +71 -0
- data/features/documented_options.feature +73 -0
- data/features/remote_upload.feature +49 -0
- data/features/segmented_upload.feature +27 -0
- data/features/segmented_upload_options.feature +65 -0
- data/features/step_definitions/orbit_steps.rb +194 -0
- data/features/step_definitions/uricp_steps.rb +34 -0
- data/features/support/env.rb +20 -0
- data/features/userid_download.feature +21 -0
- data/lib/segment_upload.rb +38 -0
- data/lib/uricp/curl_primitives.rb +45 -0
- data/lib/uricp/orbit_auth.rb +59 -0
- data/lib/uricp/segmenter.rb +88 -0
- data/lib/uricp/strategy/cache_common.rb +48 -0
- data/lib/uricp/strategy/cached_get.rb +48 -0
- data/lib/uricp/strategy/cleaner.rb +28 -0
- data/lib/uricp/strategy/common.rb +108 -0
- data/lib/uricp/strategy/local_convert.rb +36 -0
- data/lib/uricp/strategy/local_convert_common.rb +29 -0
- data/lib/uricp/strategy/local_link.rb +31 -0
- data/lib/uricp/strategy/piped_cache.rb +40 -0
- data/lib/uricp/strategy/piped_cache_convert.rb +45 -0
- data/lib/uricp/strategy/piped_compress.rb +30 -0
- data/lib/uricp/strategy/piped_decompress.rb +35 -0
- data/lib/uricp/strategy/piped_local_compress.rb +30 -0
- data/lib/uricp/strategy/piped_local_get.rb +29 -0
- data/lib/uricp/strategy/piped_local_put.rb +29 -0
- data/lib/uricp/strategy/piped_remote_get.rb +38 -0
- data/lib/uricp/strategy/pruner.rb +22 -0
- data/lib/uricp/strategy/remote_put.rb +47 -0
- data/lib/uricp/strategy/segmented_remote_put.rb +52 -0
- data/lib/uricp/strategy/sweeper.rb +28 -0
- data/lib/uricp/uri_strategy.rb +47 -0
- data/lib/uricp/version.rb +4 -0
- data/lib/uricp.rb +56 -0
- data/spec/something_spec.rb +5 -0
- data/uricp.gemspec +32 -0
- metadata +281 -0
@@ -0,0 +1,65 @@
|
|
1
|
+
Feature: Documented Help for Segmented Upload
|
2
|
+
In order to work out what segment_upload does
|
3
|
+
As a command line user
|
4
|
+
I want to get detailed usage information
|
5
|
+
|
6
|
+
Scenario: Basic UI
|
7
|
+
When I get help for "segment_upload"
|
8
|
+
Then the exit status should be 0
|
9
|
+
And the banner should include the version
|
10
|
+
And the banner should document that this app takes options
|
11
|
+
And the following options should be documented:
|
12
|
+
|--version|
|
13
|
+
|--auth-token|
|
14
|
+
|--auth-user|
|
15
|
+
|--auth-key|
|
16
|
+
|--from|
|
17
|
+
|--segment-size|
|
18
|
+
And the option "dry-run" should be documented which is negatable
|
19
|
+
And the banner should document that this app's arguments are:
|
20
|
+
|to_uri|which is required|
|
21
|
+
|
22
|
+
Scenario: auth token and auth-user are mutually exclusive
|
23
|
+
When I run `segment_upload --auth-token abcdef --auth-user xyz --auth-key abc http://source file:///target`
|
24
|
+
Then the exit status should not be 0
|
25
|
+
And the stderr should contain "needless argument"
|
26
|
+
|
27
|
+
Scenario: auth user needs auth key
|
28
|
+
When I run `segment_upload --auth-user xyz http://source`
|
29
|
+
Then the exit status should not be 0
|
30
|
+
And the stderr should contain "missing argument"
|
31
|
+
|
32
|
+
Scenario: auth key needs auth user
|
33
|
+
When I run `segment_upload --auth-user xyz http://source`
|
34
|
+
Then the exit status should not be 0
|
35
|
+
And the stderr should contain "missing argument"
|
36
|
+
|
37
|
+
Scenario: needs some sort of authentication
|
38
|
+
When I run `segment_upload http://source`
|
39
|
+
Then the exit status should not be 0
|
40
|
+
And the stderr should contain "missing argument"
|
41
|
+
|
42
|
+
Scenario: no http should fail
|
43
|
+
When I run `segment_upload ftp://source`
|
44
|
+
Then the exit status should not be 0
|
45
|
+
And the stderr should contain "unsupported url"
|
46
|
+
|
47
|
+
Scenario: authentication with normal http should fail
|
48
|
+
When I run `segment_upload --auth-user cli-xxxxx --auth-key fred https://foss-lab-manual.googlecode.com/files`
|
49
|
+
Then the exit status should not be 0
|
50
|
+
And the stderr should contain "Cannot authenticate"
|
51
|
+
|
52
|
+
Scenario: bad authentication should fail against orbit
|
53
|
+
When I run `segment_upload --auth-user cli-xxxxx --auth-key fred https://orbit.brightbox.com/v1/acc-xxxxx/test/test.img`
|
54
|
+
Then the exit status should not be 0
|
55
|
+
And the stderr should contain "Cannot authenticate"
|
56
|
+
|
57
|
+
Scenario: should not accept plain number for segment size
|
58
|
+
When I run `segment_upload --segment-size 100 --auth-token abcdef https:///orbit.brightbox.com/v1/acc-xxxxx/test/test.img`
|
59
|
+
Then the exit status should not be 0
|
60
|
+
And the stderr should contain "Unparseable filesize"
|
61
|
+
|
62
|
+
Scenario: should trap attempts to open a command
|
63
|
+
When I run `segment_upload --segment-size 100MB --auth-token abcdef --from ' |fred' https:///orbit.brightbox.com/v1/acc-xxxxx/test/test.img`
|
64
|
+
Then the exit status should not be 0
|
65
|
+
And the stderr should contain "invalid argument"
|
@@ -0,0 +1,194 @@
|
|
1
|
+
require 'inifile'
|
2
|
+
|
3
|
+
def obtain_credentials
|
4
|
+
config = IniFile.load(ENV['HOME']+ '/.brightbox/config') ||
|
5
|
+
raise(RuntimeError, "No brightbox config in home directory. Can't obtain auth token for tests")
|
6
|
+
section = config.sections.first
|
7
|
+
[config[section]['client_id'], config[section]['secret']]
|
8
|
+
end
|
9
|
+
|
10
|
+
def fetch_orbit_token
|
11
|
+
@clientid, @key = obtain_credentials
|
12
|
+
cmd = "curl -I https://orbit.brightbox.com/v1 -H 'X-Auth-User: #{@clientid}' -H 'X-Auth-Key: #{@key}'"
|
13
|
+
run_simple(unescape(cmd))
|
14
|
+
stdout_from(cmd).each_line do |line|
|
15
|
+
key, value = line.strip.split(/\s*:\s*/,2)
|
16
|
+
@current_auth_token = value if key == 'X-Auth-Token'
|
17
|
+
@current_storage_url = value if key == 'X-Storage-Url'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_download_file(filetype, name, container)
|
22
|
+
upload_url = File.join(@current_storage_url, container, name)
|
23
|
+
tempfile = '/tmp/fred55322'
|
24
|
+
FileUtils.rm_f(Dir.glob(tempfile+'*'))
|
25
|
+
format = 'raw'
|
26
|
+
format = 'qcow2' if filetype == 'qcow2'
|
27
|
+
cmd = "qemu-img create -q -f #{format} #{tempfile} 1G"
|
28
|
+
system(unescape(cmd))
|
29
|
+
cmd = "cat #{tempfile}"
|
30
|
+
if filetype == 'lz4'
|
31
|
+
cmd += " | lz4c "
|
32
|
+
end
|
33
|
+
cmd += "| curl --silent --fail -T - -H 'X-Auth-Token: #{@current_auth_token}' #{upload_url} "
|
34
|
+
system(unescape(cmd))
|
35
|
+
end
|
36
|
+
|
37
|
+
def create_container(name)
|
38
|
+
url = File.join(@current_storage_url, name)
|
39
|
+
cmd = "curl --silent --fail -I -H 'X-Auth-Token: #{@current_auth_token}' #{url}>/dev/null || curl --silent --fail -I -H 'X-Auth-Token: #{@current_auth_token}' #{url} -X PUT"
|
40
|
+
system(unescape(cmd))
|
41
|
+
end
|
42
|
+
|
43
|
+
Given(/^a container called "([^"]*)"$/) do |container|
|
44
|
+
create_container(container)
|
45
|
+
end
|
46
|
+
|
47
|
+
Given(/^a cache of "(.*?)" from container "(.*?)" at "(.*?)"$/) do |name, container, cache|
|
48
|
+
url = File.join(@current_storage_url, container, name)
|
49
|
+
target = File.join(cache, 'cache', name)
|
50
|
+
steps %{
|
51
|
+
When I successfully run `curl --fail --silent -o #{target} -H 'X-Auth-Token: #{@current_auth_token}' #{url}`
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
Given /^a (\d+) byte lz4 file named "([^"]*)"$/ do |file_size, file_name|
|
56
|
+
write_fixed_size_file(file_name, file_size.to_i)
|
57
|
+
system("lz4c #{file_name}")
|
58
|
+
File.rename file_name+'.lz4', file_name
|
59
|
+
end
|
60
|
+
|
61
|
+
When(/^I store "([^"]*)" into container "([^"]*)" from "([^"]*)"$/) do |name, container, source|
|
62
|
+
steps %{
|
63
|
+
When I store "#{name}" with options "" into container "#{container}" from "#{source}"
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
When(/^I retrieve "([^"]*)" with options "([^"]*)" from container "([^"]*)" into "([^"]*)"$/) do |name, runoptions, container, target|
|
68
|
+
url = File.join(@current_storage_url, container, name)
|
69
|
+
steps %{
|
70
|
+
When I successfully run `uricp #{runoptions} --auth-token #{@current_auth_token} #{url} #{target}`
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
When(/^I retrieve "([^"]*)" from container "([^"]*)" into "([^"]*)" with a userid$/) do |name, container, target|
|
75
|
+
url = File.join(@current_storage_url, container, name)
|
76
|
+
steps %{
|
77
|
+
When I successfully run `uricp --auth-user '#{@clientid}' --auth-key '#{@key}' #{url} #{target}`
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
When(/^I store "([^"]*)" into container "([^"]*)" from "([^"]*)" with a userid$/) do |name, container, source|
|
82
|
+
@current_container = container
|
83
|
+
@current_name = name
|
84
|
+
url = File.join(@current_storage_url, container, name)
|
85
|
+
steps %{
|
86
|
+
When I successfully run `uricp --auth-user '#{@clientid}' --auth-key '#{@key}' #{source} #{url}`
|
87
|
+
}
|
88
|
+
end
|
89
|
+
|
90
|
+
When(/^I retrieve "([^"]*)" from container "([^"]*)" into "([^"]*)"$/) do |name, container, target|
|
91
|
+
steps %{
|
92
|
+
When I retrieve "#{name}" with options "" from container "#{container}" into "#{target}"
|
93
|
+
}
|
94
|
+
end
|
95
|
+
|
96
|
+
When(/^I store "([^"]*)" with options "([^"]*)" into container "([^"]*)" from "([^"]*)"$/) do |name, runoptions, container, source|
|
97
|
+
@current_container = container
|
98
|
+
@current_name = name
|
99
|
+
url = File.join(@current_storage_url, container, name)
|
100
|
+
steps %{
|
101
|
+
When I successfully run `uricp #{runoptions} --auth-token #{@current_auth_token} #{source} #{url}`
|
102
|
+
}
|
103
|
+
end
|
104
|
+
|
105
|
+
When(/^I store "([^"]*)" with segment size "([^"]*)" into container "([^"]*)" from "([^"]*)" with a userid$/) do |name, segment_size, container, source|
|
106
|
+
@current_container = container
|
107
|
+
@current_name = name
|
108
|
+
url = File.join(@current_storage_url, container, name)
|
109
|
+
steps %{
|
110
|
+
When I successfully run `uricp --auth-user '#{@clientid}' --auth-key '#{@key}' --segment-size '#{segment_size}' #{source} #{url}`
|
111
|
+
}
|
112
|
+
end
|
113
|
+
|
114
|
+
When(/^I store "([^"]*)" with segment size "([^"]*)" and options "([^"]*)" into container "([^"]*)" from "([^"]*)" with a userid$/) do |name, segment_size, runoptions, container, source|
|
115
|
+
@current_container = container
|
116
|
+
@current_name = name
|
117
|
+
url = File.join(@current_storage_url, container, name)
|
118
|
+
steps %{
|
119
|
+
When I successfully run `uricp #{runoptions} --auth-user '#{@clientid}' --auth-key '#{@key}' --segment-size '#{segment_size}' #{source} #{url}`
|
120
|
+
}
|
121
|
+
end
|
122
|
+
|
123
|
+
When(/^I upload "([^"]*)" with segment size "([^"]*)" into container "([^"]*)" from "([^"]*)"$/) do |name, segment_size, container, source|
|
124
|
+
@current_container = container
|
125
|
+
@current_name = name
|
126
|
+
url = File.join(@current_storage_url, container, name)
|
127
|
+
steps %{
|
128
|
+
When I successfully run `segment_upload --from #{source} --auth-token #{@current_auth_token} --segment-size #{segment_size} #{url}`
|
129
|
+
}
|
130
|
+
end
|
131
|
+
|
132
|
+
When(/^I upload "([^"]*)" with segment size "([^"]*)" into container "([^"]*)" from "([^"]*)" as a stream$/) do |name, segment_size, container, source|
|
133
|
+
@current_container = container
|
134
|
+
@current_name = name
|
135
|
+
url = File.join(@current_storage_url, container, name)
|
136
|
+
steps %{
|
137
|
+
When I successfully run `sh -c 'cat #{source} | segment_upload --auth-token #{@current_auth_token} --segment-size #{segment_size} #{url}'`
|
138
|
+
}
|
139
|
+
end
|
140
|
+
|
141
|
+
Then(/^a (\d+) byte entry should exist in container "(.*?)" called "(.*?)"$/) do |size, container, name|
|
142
|
+
url = File.join(@current_storage_url, container, name)
|
143
|
+
command = "curl -I --fail --silent -H 'X-Auth-Token:#{@current_auth_token}' #{url}"
|
144
|
+
steps %{
|
145
|
+
When I successfully run `#{command}`
|
146
|
+
Then the output should match /\\sContent-Length: #{size}\\s/
|
147
|
+
}
|
148
|
+
end
|
149
|
+
|
150
|
+
Then(/^the container "(.*?)" should contain (\d+) entries$/) do |container, quantity|
|
151
|
+
url = File.join(@current_storage_url, container)
|
152
|
+
command = "curl -I --fail --silent -H 'X-Auth-Token:#{@current_auth_token}' #{url}"
|
153
|
+
steps %{
|
154
|
+
When I successfully run `#{command}`
|
155
|
+
Then the output should match /\\sX-Container-Object-Count: #{quantity}\\s/
|
156
|
+
}
|
157
|
+
end
|
158
|
+
|
159
|
+
Then(/^an lz4 compressed entry should exist in container "(.*?)" called "(.*?)"$/) do |container, name|
|
160
|
+
url = File.join(@current_storage_url, container, name)
|
161
|
+
cmd="curl -r 0-3 --fail --silent -H X-Auth-Token:#{@current_auth_token} #{url}"
|
162
|
+
run_simple(unescape(cmd))
|
163
|
+
assert_exact_output([0x184D2204].pack('V'), stdout_from(cmd))
|
164
|
+
end
|
165
|
+
|
166
|
+
Then(/^a qcow2 entry should exist in container "(.*?)" called "(.*?)"$/) do |container, name|
|
167
|
+
url = File.join(@current_storage_url, container, name)
|
168
|
+
cmd="curl -r 0-3 --fail --silent -H X-Auth-Token:#{@current_auth_token} #{url}"
|
169
|
+
run_simple(unescape(cmd))
|
170
|
+
assert_exact_output(['QFI',0xfb].pack('a3C'), stdout_from(cmd))
|
171
|
+
end
|
172
|
+
|
173
|
+
Before('@orbit') do
|
174
|
+
fetch_orbit_token
|
175
|
+
end
|
176
|
+
|
177
|
+
Before('@orbitdownloads') do
|
178
|
+
$orbit_setup ||= false
|
179
|
+
unless $orbit_setup
|
180
|
+
create_container('test')
|
181
|
+
create_download_file('qcow2', 'img-qcow2', 'test')
|
182
|
+
create_download_file('lz4', 'img-lz4cy', 'test')
|
183
|
+
$orbit_setup = true
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
After('@orbit') do
|
188
|
+
if @current_auth_token && @current_container
|
189
|
+
steps %{
|
190
|
+
When I successfully run `swift --os-storage-url=#{@current_storage_url} --os-auth-token=#{@current_auth_token} delete #{@current_container} #{@current_name}`
|
191
|
+
}
|
192
|
+
@current_container = @current_name = nil
|
193
|
+
end
|
194
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
Then(/^the file named "(.*?)" should have a file format of "(.*?)"$/) do |filename, format|
|
2
|
+
case format
|
3
|
+
when 'lz4'
|
4
|
+
assert_exact_output([0x184D2204].pack('V'),
|
5
|
+
File.open(filename, 'rb') {|f| f.read(4) })
|
6
|
+
when 'qcow2v3', 'qcow3'
|
7
|
+
steps %{
|
8
|
+
When I successfully run `qemu-img info #{filename}`
|
9
|
+
Then the output from "qemu-img info #{filename}" should contain "file format: qcow2"
|
10
|
+
And the output from "qemu-img info #{filename}" should contain "compat: 1.1"
|
11
|
+
}
|
12
|
+
else
|
13
|
+
assert_no_partial_output([0x184D2204].pack('V'),
|
14
|
+
File.open(filename, 'rb') {|f| f.read(4) })
|
15
|
+
steps(%{
|
16
|
+
When I successfully run `qemu-img info #{filename}`
|
17
|
+
Then the output from "qemu-img info #{filename}" should contain "file format: #{format}"
|
18
|
+
And the output from "qemu-img info #{filename}" should not contain "compat: 1.1"
|
19
|
+
})
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
Given(/^a correctly initialised cache at "(.*?)"$/) do |basedir|
|
24
|
+
steps %{
|
25
|
+
Given a directory named "#{basedir}/cache"
|
26
|
+
And a directory named "#{basedir}/temp"
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
Given(/^an empty directory named "([^"]*)"$/) do |dir_name|
|
31
|
+
create_dir(dir_name)
|
32
|
+
FileUtils.rm_rf(File.join(dir_name, '.'), :secure => true)
|
33
|
+
end
|
34
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'aruba/cucumber'
|
2
|
+
require 'methadone/cucumber'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
ENV['PATH'] = "#{File.expand_path(File.dirname(__FILE__) + '/../../bin')}#{File::PATH_SEPARATOR}#{ENV['PATH']}"
|
6
|
+
LIB_DIR = File.join(File.expand_path(File.dirname(__FILE__)),'..','..','lib')
|
7
|
+
|
8
|
+
Before do
|
9
|
+
# Using "announce" causes massive warnings on 1.9.2
|
10
|
+
@puts = true
|
11
|
+
@original_rubylib = ENV['RUBYLIB']
|
12
|
+
ENV['RUBYLIB'] = LIB_DIR + File::PATH_SEPARATOR + ENV['RUBYLIB'].to_s
|
13
|
+
end
|
14
|
+
|
15
|
+
After do
|
16
|
+
ENV['RUBYLIB'] = @original_rubylib
|
17
|
+
|
18
|
+
FileUtils.rm_rf(Dir.glob('/tmp/uricp/*'))
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
@orbit
|
2
|
+
@orbitdownloads
|
3
|
+
Feature: User authenticated download of images from orbit
|
4
|
+
In order to download images from orbit
|
5
|
+
As a command line user
|
6
|
+
I want to retrieve the URI via an optional cache and copy correctly to target in the right format using a user id directly
|
7
|
+
|
8
|
+
Background:
|
9
|
+
Given an empty directory named "/tmp/uricp"
|
10
|
+
And the default aruba timeout is 15 seconds
|
11
|
+
And a container called "test"
|
12
|
+
|
13
|
+
Scenario: qcow download no conversion, no cache with userid
|
14
|
+
When I retrieve "img-qcow2" from container "test" into "file:///tmp/uricp/srv-test1" with a userid
|
15
|
+
Then a file named "/tmp/uricp/srv-test1" should exist
|
16
|
+
And the file named "/tmp/uricp/srv-test1" should have a file format of "qcow2v3"
|
17
|
+
|
18
|
+
Scenario: direct upload with userid
|
19
|
+
Given a 102400 byte file named "/tmp/uricp/srv-testy"
|
20
|
+
When I store "img-usrid" into container "test" from "file:///tmp/uricp/srv-testy" with a userid
|
21
|
+
Then a 102400 byte entry should exist in container "test" called "img-usrid"
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "uricp/version"
|
2
|
+
require "uricp/curl_primitives"
|
3
|
+
require "uricp/orbit_auth"
|
4
|
+
require "uricp/segmenter"
|
5
|
+
|
6
|
+
module Uricp
|
7
|
+
|
8
|
+
UnsupportedURLtype = Class.new(ArgumentError)
|
9
|
+
|
10
|
+
end
|
11
|
+
|
12
|
+
#Monkey patch a copy_stream facility in using 'sendfile'
|
13
|
+
unless IO.respond_to? :copy_stream
|
14
|
+
require 'sendfile'
|
15
|
+
|
16
|
+
def IO.copy_stream(src, dst, copy_length=nil, offset=nil)
|
17
|
+
unless src.stat.pipe?
|
18
|
+
current_pos = src.pos
|
19
|
+
count = dst.sendfile(src, offset || current_pos, copy_length)
|
20
|
+
src.seek(count, IO::SEEK_CUR)
|
21
|
+
return count
|
22
|
+
else
|
23
|
+
amount = copy_length.to_i
|
24
|
+
buf_size = 2**16
|
25
|
+
buffer=""
|
26
|
+
while amount > 0 do
|
27
|
+
src.read(buf_size, buffer)
|
28
|
+
amount_read = buffer.length
|
29
|
+
dst.write(buffer)
|
30
|
+
amount -= amount_read
|
31
|
+
break if src.eof?
|
32
|
+
end
|
33
|
+
return copy_length.to_i - amount
|
34
|
+
end
|
35
|
+
rescue EOFError
|
36
|
+
0
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Uricp::CurlPrimitives
|
2
|
+
|
3
|
+
attr_reader :options
|
4
|
+
|
5
|
+
def from
|
6
|
+
options['from_uri']
|
7
|
+
end
|
8
|
+
|
9
|
+
def from=(target)
|
10
|
+
options['from_uri'] = target
|
11
|
+
end
|
12
|
+
|
13
|
+
def to
|
14
|
+
options['to_uri']
|
15
|
+
end
|
16
|
+
|
17
|
+
def to=(target)
|
18
|
+
options['to_uri'] = target
|
19
|
+
end
|
20
|
+
|
21
|
+
def curl_command
|
22
|
+
"curl --fail --silent"
|
23
|
+
end
|
24
|
+
|
25
|
+
def authentication
|
26
|
+
"-H X-Auth-Token:#{options['auth-token']}" if http_authentication?
|
27
|
+
end
|
28
|
+
|
29
|
+
def http_authentication?
|
30
|
+
options['auth-token']
|
31
|
+
end
|
32
|
+
|
33
|
+
def curl_upload_from(source, destination = to)
|
34
|
+
"#{curl_command} #{authentication} -T #{source} #{destination.to_s};"
|
35
|
+
end
|
36
|
+
|
37
|
+
def curl_download_to_pipe
|
38
|
+
"#{curl_command} #{authentication} #{from.to_s} |"
|
39
|
+
end
|
40
|
+
|
41
|
+
def curl_manifest(object_manifest, destination = to)
|
42
|
+
"#{curl_command} #{authentication} -X PUT -H 'X-Object-Manifest: #{object_manifest}' #{destination.to_s} --data-binary ''"
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
module Uricp
|
3
|
+
|
4
|
+
class OrbitAuth
|
5
|
+
|
6
|
+
AuthenticationFailure = Class.new(ArgumentError)
|
7
|
+
|
8
|
+
def initialize(auth_uri, auth_id, auth_key)
|
9
|
+
auth_uri.open(
|
10
|
+
'X-Auth-User' => auth_id,
|
11
|
+
'X-Auth-Key' => auth_key,
|
12
|
+
'Range' => 'bytes=0-0'
|
13
|
+
) do |uri|
|
14
|
+
@storage_url = uri.meta['x-storage-url']
|
15
|
+
@token = uri.meta['x-auth-token']
|
16
|
+
end
|
17
|
+
rescue OpenURI::HTTPError => e
|
18
|
+
raise AuthenticationFailure, "Cannot authenticate against #{auth_uri}"
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :storage_url, :token
|
22
|
+
|
23
|
+
def self.validate_options(options)
|
24
|
+
if options['auth-token'] && (options['auth-key'] || options['auth-user'])
|
25
|
+
raise ::OptionParser::NeedlessArgument,
|
26
|
+
"use either key based or token based authentication"
|
27
|
+
end
|
28
|
+
if options['auth-key'].nil? ^ options['auth-user'].nil?
|
29
|
+
raise ::OptionParser::MissingArgument,
|
30
|
+
"'auth-user' requires 'auth-key'"
|
31
|
+
end
|
32
|
+
if (options['auth-token'] || options['auth-user']) && options['auth_uri'].nil?
|
33
|
+
raise ::OptionParser::NeedlessArgument,
|
34
|
+
"authentication is for http uris only"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.add_auth_token(options)
|
39
|
+
if options['auth-user']
|
40
|
+
orbit_credentials = self.new(options['auth_uri'],
|
41
|
+
options['auth-user'], options['auth-key'])
|
42
|
+
options['auth-token'] = orbit_credentials.token
|
43
|
+
options.delete('auth-key')
|
44
|
+
options.delete('auth-user')
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.add_auth_to_optionparser(app)
|
49
|
+
app.on("--auth-token AUTH_TOKEN",
|
50
|
+
"Use AUTH_TOKEN for non-local requests")
|
51
|
+
app.on("--auth-user AUTH_USER",
|
52
|
+
"Use AUTH_USER for authentication")
|
53
|
+
app.on("--auth-key AUTH_KEY",
|
54
|
+
"Use AUTH_KEY for authentication")
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module Uricp
|
4
|
+
|
5
|
+
class Segmenter
|
6
|
+
|
7
|
+
include Methadone::CLILogging
|
8
|
+
include Methadone::SH
|
9
|
+
include Uricp::CurlPrimitives
|
10
|
+
|
11
|
+
def initialize(options)
|
12
|
+
@options = options
|
13
|
+
source = options['from']
|
14
|
+
if @source = source && open(source)
|
15
|
+
@stream = File.pipe?(source) || File.chardev?(source)
|
16
|
+
else
|
17
|
+
@source = STDIN
|
18
|
+
@stream = true
|
19
|
+
end
|
20
|
+
split_path
|
21
|
+
end
|
22
|
+
|
23
|
+
def split_path
|
24
|
+
elements = Pathname.new(to.path).enum_for(:each_filename).to_a
|
25
|
+
elements.shift
|
26
|
+
@account=elements.shift
|
27
|
+
@container=elements.shift
|
28
|
+
@object_path=File.join(elements)
|
29
|
+
end
|
30
|
+
|
31
|
+
def upload
|
32
|
+
if large_upload?
|
33
|
+
segmented_upload
|
34
|
+
else
|
35
|
+
sh! curl_upload_from(options['from']),
|
36
|
+
:on_fail => "Upload to #{to} failed"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def segment_size
|
41
|
+
options['segment-size'].to_i
|
42
|
+
end
|
43
|
+
|
44
|
+
def stream?
|
45
|
+
@stream
|
46
|
+
end
|
47
|
+
|
48
|
+
def large_upload?
|
49
|
+
stream? || @source.stat.size > segment_size
|
50
|
+
end
|
51
|
+
|
52
|
+
def manifest_suffix
|
53
|
+
@manifest_suffix ||= [Time.now.to_f, @source.stat.size, segment_size].join('/')
|
54
|
+
end
|
55
|
+
|
56
|
+
def object_manifest
|
57
|
+
@object_manifest ||= File.join(@container, @object_path, manifest_suffix)+'/'
|
58
|
+
end
|
59
|
+
|
60
|
+
def segmented_upload
|
61
|
+
debug "#{self.class.name}: Large upload detected - segmenting into #{segment_size} byte chunks."
|
62
|
+
suffix = 0
|
63
|
+
until @source.eof?
|
64
|
+
debug "#{self.class.name}: Uploading segment #{suffix}"
|
65
|
+
upload_segment(suffix)
|
66
|
+
suffix = suffix.next
|
67
|
+
end
|
68
|
+
add_manifest
|
69
|
+
end
|
70
|
+
|
71
|
+
def upload_segment(segment_number)
|
72
|
+
segment_name = File.join(to.to_s, manifest_suffix, '%08d' % segment_number)
|
73
|
+
debug "Uploading with #{curl_upload_from('-', segment_name)}"
|
74
|
+
open('|'+curl_upload_from('-', segment_name), 'w') do |destination|
|
75
|
+
copy_length = IO.copy_stream(@source, destination, segment_size)
|
76
|
+
debug "#{self.class.name}: Uploaded #{copy_length} bytes to #{segment_name}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def add_manifest
|
81
|
+
debug "Adding DLO object_manifest #{object_manifest}"
|
82
|
+
sh! curl_manifest(object_manifest),
|
83
|
+
:on_fail => "Upload to #{to} failed"
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module Uricp::Strategy
|
4
|
+
|
5
|
+
module CacheCommon
|
6
|
+
|
7
|
+
def validate_cache!
|
8
|
+
raise Uricp::MissingCache,
|
9
|
+
"No cache found at #{cache_root}. Expected a 'cache' and 'temp' directory" unless cache_exists?
|
10
|
+
raise Uricp::MissingCache,
|
11
|
+
"No cache name found" unless cache_name
|
12
|
+
end
|
13
|
+
|
14
|
+
def in_cache?
|
15
|
+
File.readable? cache_file
|
16
|
+
end
|
17
|
+
|
18
|
+
def cache_root
|
19
|
+
options['cache']
|
20
|
+
end
|
21
|
+
|
22
|
+
def cache_name
|
23
|
+
options['cache_name']
|
24
|
+
end
|
25
|
+
|
26
|
+
def cache_exists?
|
27
|
+
cache_root && %w{temp cache}.all? do |d|
|
28
|
+
File.directory?(File.join(cache_root, d))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def temp_cache_file
|
33
|
+
@temp_cache_file ||= File.join(cache_root, 'temp', cache_name)
|
34
|
+
end
|
35
|
+
|
36
|
+
def temp_cache_uri
|
37
|
+
URI.join('file:///', temp_cache_file)
|
38
|
+
end
|
39
|
+
|
40
|
+
def cache_file
|
41
|
+
@cache_file ||= File.join(cache_root, 'cache', cache_name)
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module Uricp::Strategy
|
4
|
+
|
5
|
+
class CachedGet
|
6
|
+
|
7
|
+
include Uricp::Strategy::Common
|
8
|
+
include Uricp::Strategy::CacheCommon
|
9
|
+
|
10
|
+
def appropriate?
|
11
|
+
if cache_root
|
12
|
+
validate_cache!
|
13
|
+
if in_cache? || file_source?
|
14
|
+
return proposal
|
15
|
+
else
|
16
|
+
debug "#{self.class.name}: no cache entry for #{options['from_uri']}"
|
17
|
+
end
|
18
|
+
else
|
19
|
+
debug "#{self.class.name}: not appropriate"
|
20
|
+
end
|
21
|
+
false
|
22
|
+
end
|
23
|
+
|
24
|
+
def command
|
25
|
+
":;"
|
26
|
+
end
|
27
|
+
|
28
|
+
def proposal
|
29
|
+
@proposed_options = options.dup
|
30
|
+
unless file_source?
|
31
|
+
@proposed_options['from_uri'] = URI.join('file:///', cache_file)
|
32
|
+
end
|
33
|
+
@proposed_options.delete('cache')
|
34
|
+
@proposed_options.delete('cache_name')
|
35
|
+
if conversion_required?
|
36
|
+
@proposed_options['source-format'] =
|
37
|
+
File.open(@proposed_options['from_uri'].path) {|f| encoding(f)}
|
38
|
+
if @proposed_options['source-format'] == @proposed_options['target-format']
|
39
|
+
@proposed_options.delete('source-format')
|
40
|
+
@proposed_options.delete('target-format')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|