rack2aws 0.1.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.
- checksums.yaml +7 -0
- data/bin/rack2aws +5 -0
- data/lib/rack2aws.rb +130 -0
- data/lib/rack2aws/config.rb +187 -0
- data/lib/rack2aws/errors.rb +2 -0
- data/lib/rack2aws/props_reader.rb +19 -0
- data/lib/rack2aws/version.rb +3 -0
- metadata +107 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6b4761ec5789bc29a4d5fc2d56799a32b149be32
|
4
|
+
data.tar.gz: 63c2dadc08a92138f0e249732aa870d250c41636
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: acb2647fd5e88e139bce7e1eff15aac44333ac6753b555ffa607702b90975a151f00e502301557bfc1a2596578416cdd15d6825286674cb0bbca545f550e7b5f
|
7
|
+
data.tar.gz: c3d38eea9c6ab2591022869c828e791c63666c5e8d5d35a06ed46f04c7834c8f12026c2915113db4766425ab7bcd819365bab7f18e34bfa392700efdc5fd151b
|
data/bin/rack2aws
ADDED
data/lib/rack2aws.rb
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'fog'
|
2
|
+
require 'commander'
|
3
|
+
require 'rack2aws/config'
|
4
|
+
require 'rack2aws/version'
|
5
|
+
|
6
|
+
|
7
|
+
module Rack2Aws
|
8
|
+
class FileCopy
|
9
|
+
include Rack2Aws::Configuration
|
10
|
+
|
11
|
+
attr_reader :per_page, :rackspace, :aws, :rackspace_directory, :aws_directory, :verbose_mode, :files, :total
|
12
|
+
|
13
|
+
def initialize(options={})
|
14
|
+
options = default_options.merge(options)
|
15
|
+
@per_page = options[:per_page]
|
16
|
+
@rackspace = Fog::Storage.new(RackspaceConfig.load())
|
17
|
+
@aws = Fog::Storage.new(AWSConfig.load())
|
18
|
+
|
19
|
+
@rackspace_directory = rackspace.directories.get(options[:rackspace_container])
|
20
|
+
@aws_directory = aws.directories.get(options[:aws_bucket])
|
21
|
+
@verbose_mode = options[:verbose]
|
22
|
+
@files = []
|
23
|
+
@total = 0
|
24
|
+
end
|
25
|
+
|
26
|
+
def default_options
|
27
|
+
{ :per_page => 10000 }
|
28
|
+
end
|
29
|
+
|
30
|
+
def copy
|
31
|
+
time = Time.now
|
32
|
+
pages = rackspace_directory.count / per_page + 1
|
33
|
+
marker = ''
|
34
|
+
|
35
|
+
# get Rackspace files
|
36
|
+
pages.times do |i|
|
37
|
+
puts "! Getting page #{i+1}..."
|
38
|
+
files = rackspace_directory.files.all(:limit => per_page, :marker => marker).to_a
|
39
|
+
puts "! #{files.size} files in page #{i+1}, forking..." if verbose_mode
|
40
|
+
pid = fork do
|
41
|
+
copy_files(i, files)
|
42
|
+
end
|
43
|
+
puts "! Process #{pid} forked to copy files" if verbose_mode
|
44
|
+
marker = files.last.key
|
45
|
+
@total += files.size
|
46
|
+
end
|
47
|
+
|
48
|
+
pages.times do
|
49
|
+
Process.wait
|
50
|
+
end
|
51
|
+
|
52
|
+
puts "--------------------------------------------------"
|
53
|
+
puts "! #{total} files copied in #{Time.now - time}secs."
|
54
|
+
puts "--------------------------------------------------\n\n"
|
55
|
+
end
|
56
|
+
|
57
|
+
def copy_files(page, files)
|
58
|
+
puts " [#{Process.pid}] Page #{page+1}: Copying #{files.size} files..." if verbose_mode
|
59
|
+
total = files.size
|
60
|
+
max_processes = 4
|
61
|
+
process_pids = {}
|
62
|
+
time = Time.now
|
63
|
+
|
64
|
+
while !files.empty? or !process_pids.empty?
|
65
|
+
while process_pids.size < max_processes and files.any? do
|
66
|
+
file = files.pop
|
67
|
+
pid = Process.fork do
|
68
|
+
copy_file(file)
|
69
|
+
end
|
70
|
+
process_pids[pid] = { :file => file }
|
71
|
+
end
|
72
|
+
|
73
|
+
if pid_done = Process.wait
|
74
|
+
if job_finished = process_pids.delete(pid_done)
|
75
|
+
puts " [#{Process.pid}] Page #{page+1}: Copied #{job_finished[:file].key}." if verbose_mode
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
puts " [#{Process.pid}] ** Page #{page+1}: Copied #{total} files in #{Time.now - time}secs" if verbose_mode
|
81
|
+
end
|
82
|
+
|
83
|
+
def copy_file(file)
|
84
|
+
aws_directory.files.create(:key => file.key,
|
85
|
+
:body => file.body,
|
86
|
+
:content_type => file,
|
87
|
+
:public => false)
|
88
|
+
end
|
89
|
+
|
90
|
+
private :copy_files, :copy_file
|
91
|
+
end
|
92
|
+
|
93
|
+
class CLI
|
94
|
+
include Commander::Methods
|
95
|
+
|
96
|
+
def run
|
97
|
+
program :name, 'rack2aws'
|
98
|
+
program :version, Rack2Aws::VERSION
|
99
|
+
program :description, 'Bridge from Rackspace Cloud Files to AWS S3'
|
100
|
+
program :help, 'Author', 'Faissal Elamraoui <amr.faissal@gmail.com>'
|
101
|
+
|
102
|
+
global_option('--verbose', 'Explain what is being done') { $verbose = true }
|
103
|
+
|
104
|
+
command :port do |cmd|
|
105
|
+
cmd.syntax = 'rack2aws port [options]'
|
106
|
+
cmd.description = 'Port files from Rackspace Cloud Files(tm) to AWS S3'
|
107
|
+
|
108
|
+
cmd.option '--container CONTAINER_NAME', String, 'Rackspace Cloud Files container name'
|
109
|
+
cmd.option '--bucket BUCKET_NAME', String, 'AWS S3 bucket name'
|
110
|
+
cmd.action do |args, options|
|
111
|
+
if options.container.nil?
|
112
|
+
options.container = ask('Rackspace Cloud Files container: ')
|
113
|
+
end
|
114
|
+
|
115
|
+
if options.bucket.nil?
|
116
|
+
options.bucket = ask('AWS S3 bucket: ')
|
117
|
+
end
|
118
|
+
|
119
|
+
FileCopy.new({
|
120
|
+
:rackspace_container => options.container,
|
121
|
+
:aws_bucket => options.bucket,
|
122
|
+
:verbose => $verbose
|
123
|
+
}).copy
|
124
|
+
end
|
125
|
+
end
|
126
|
+
run!
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
require 'rack2aws/props_reader'
|
2
|
+
require 'rack2aws/errors'
|
3
|
+
|
4
|
+
|
5
|
+
# Class to parse configuration files in the format of "param = value".
|
6
|
+
class KVConfigParser
|
7
|
+
attr_accessor :config_file, :params, :groups
|
8
|
+
|
9
|
+
# Initialize the class with the path to the 'config_fil'
|
10
|
+
# The class objects are dynamically generated by the
|
11
|
+
# name of the 'param' in the config file. Therefore, if
|
12
|
+
# the config file is 'param = value' then the itializer
|
13
|
+
# will eval "@param = value"
|
14
|
+
#
|
15
|
+
def initialize(config_file=nil, separator = '=')
|
16
|
+
@config_file = config_file
|
17
|
+
@params = {}
|
18
|
+
@groups = []
|
19
|
+
@splitRegex = '\s*' + separator + '\s*'
|
20
|
+
|
21
|
+
if(self.config_file)
|
22
|
+
self.validate_config()
|
23
|
+
self.import_config()
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Validate the config file, and contents
|
28
|
+
def validate_config()
|
29
|
+
unless File.readable?(self.config_file)
|
30
|
+
raise Errno::EACCES, "#{self.config_file} is not readable"
|
31
|
+
end
|
32
|
+
# FIX ME: need to validate contents/structure?
|
33
|
+
end
|
34
|
+
|
35
|
+
# Import data from the config to our config object.
|
36
|
+
def import_config()
|
37
|
+
# The config is top down.. anything after a [group] gets added as part
|
38
|
+
# of that group until a new [group] is found.
|
39
|
+
group = nil
|
40
|
+
open(self.config_file) {
|
41
|
+
|f|
|
42
|
+
f.each_with_index do |line, i|
|
43
|
+
line.strip!
|
44
|
+
# force_encoding not available in all versions of ruby
|
45
|
+
begin
|
46
|
+
if i.eql? 0 and line.include?("\xef\xbb\xbf".force_encoding("UTF-8"))
|
47
|
+
line.delete!("\xef\xbb\xbf".force_encoding("UTF-8"))
|
48
|
+
end
|
49
|
+
rescue NoMethodError
|
50
|
+
end
|
51
|
+
|
52
|
+
unless (/^\#/.match(line))
|
53
|
+
if(/#{@splitRegex}/.match(line))
|
54
|
+
param, value = line.split(/#{@splitRegex}/, 2)
|
55
|
+
var_name = "#{param}".chomp.strip
|
56
|
+
value = value.chomp.strip
|
57
|
+
new_value = ''
|
58
|
+
if (value)
|
59
|
+
if value =~ /^['"](.*)['"]$/
|
60
|
+
new_value = $1
|
61
|
+
else
|
62
|
+
new_value = value
|
63
|
+
end
|
64
|
+
else
|
65
|
+
new_value = ''
|
66
|
+
end
|
67
|
+
|
68
|
+
if group
|
69
|
+
self.add_to_group(group, var_name, new_value)
|
70
|
+
else
|
71
|
+
self.add(var_name, new_value)
|
72
|
+
end
|
73
|
+
|
74
|
+
elsif(/^\[(.+)\]$/.match(line).to_a != [])
|
75
|
+
group = /^\[(.+)\]$/.match(line).to_a[1]
|
76
|
+
self.add(group, {})
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
# This method will provide the value held by the object "@param"
|
84
|
+
# where "@param" is actually the name of the param in the config
|
85
|
+
# file.
|
86
|
+
#
|
87
|
+
# DEPRECATED - will be removed in future versions
|
88
|
+
#
|
89
|
+
def get_value(param)
|
90
|
+
puts "ParseConfig Deprecation Warning: get_value() is deprecated. Use " + \
|
91
|
+
"config['param'] or config['group']['param'] instead."
|
92
|
+
return self.params[param]
|
93
|
+
end
|
94
|
+
|
95
|
+
# This method is a shortcut to accessing the @params variable
|
96
|
+
def [](param)
|
97
|
+
return self.params[param]
|
98
|
+
end
|
99
|
+
|
100
|
+
# This method returns all parameters/groups defined in a config file.
|
101
|
+
def get_params()
|
102
|
+
return self.params.keys
|
103
|
+
end
|
104
|
+
|
105
|
+
# List available sub-groups of the config.
|
106
|
+
def get_groups()
|
107
|
+
return self.groups
|
108
|
+
end
|
109
|
+
|
110
|
+
# Adds an element to the config object
|
111
|
+
def add(param_name, value, override = false)
|
112
|
+
if value.class == Hash
|
113
|
+
if self.params.has_key?(param_name)
|
114
|
+
if self.params[param_name].class == Hash
|
115
|
+
if override
|
116
|
+
self.params[param_name] = value
|
117
|
+
else
|
118
|
+
self.params[param_name].merge!(value)
|
119
|
+
end
|
120
|
+
elsif self.params.has_key?(param_name)
|
121
|
+
if self.params[param_name].class != value.class
|
122
|
+
raise ArgumentError, "#{param_name} already exists, and is of different type!"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
else
|
126
|
+
self.params[param_name] = value
|
127
|
+
end
|
128
|
+
if ! self.groups.include?(param_name)
|
129
|
+
self.groups.push(param_name)
|
130
|
+
end
|
131
|
+
else
|
132
|
+
self.params[param_name] = value
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Add parameters to a group. Parameters with the same name
|
137
|
+
# could be placed in different groups
|
138
|
+
def add_to_group(group, param_name, value)
|
139
|
+
if ! self.groups.include?(group)
|
140
|
+
self.add(group, {})
|
141
|
+
end
|
142
|
+
self.params[group][param_name] = value
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
module Rack2Aws
|
148
|
+
module Configuration
|
149
|
+
|
150
|
+
class RackspaceConfig
|
151
|
+
def self.load()
|
152
|
+
config_path = "#{ENV['HOME']}/.rack/config"
|
153
|
+
|
154
|
+
if !File.exist?(config_path)
|
155
|
+
raise FileNotFoundError, "Rackspace configuration file not found"
|
156
|
+
end
|
157
|
+
|
158
|
+
props_reader = PropertiesReader.new(config_path)
|
159
|
+
return {
|
160
|
+
:provider => 'Rackspace',
|
161
|
+
:rackspace_api_key => props_reader.get("api-key"),
|
162
|
+
:rackspace_username => props_reader.get("username"),
|
163
|
+
:rackspace_region => props_reader.get("region")
|
164
|
+
}
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
class AWSConfig
|
169
|
+
def self.load()
|
170
|
+
config_path = "#{ENV['HOME']}/.aws/credentials"
|
171
|
+
|
172
|
+
if !File.exist?(config_path)
|
173
|
+
raise FileNotFoundError, "Rackspace configuration file not found".bold.red
|
174
|
+
end
|
175
|
+
|
176
|
+
credentials = KVConfigParser.new(config_path)
|
177
|
+
return {
|
178
|
+
:provider => 'AWS',
|
179
|
+
:region => credentials['default']['region'],
|
180
|
+
:aws_access_key_id => credentials['default']['aws_access_key_id'],
|
181
|
+
:aws_secret_access_key => credentials['default']['aws_secret_access_key']
|
182
|
+
}
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class PropertiesReader
|
2
|
+
def initialize(file)
|
3
|
+
@file = file
|
4
|
+
@properties = {}
|
5
|
+
IO.foreach(file) do |line|
|
6
|
+
@properties[$1.strip] = $2 if line =~ /([^=]*)=(.*)\/\/(.*)/ || line =~ /([^=]*)=(.*)/
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
output = "File Name #{@file} \n"
|
12
|
+
@properties.each {|key,value| output += "#{key}= #{value} \n"}
|
13
|
+
output
|
14
|
+
end
|
15
|
+
|
16
|
+
def get(key)
|
17
|
+
@properties[key].strip
|
18
|
+
end
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rack2aws
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Faissal Elamraoui
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-03-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: commander
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 4.4.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 4.4.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: fog
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.37.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.37.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.11'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.11'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
description:
|
70
|
+
email:
|
71
|
+
- amr.faissal@gmail.com
|
72
|
+
executables:
|
73
|
+
- rack2aws
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- bin/rack2aws
|
78
|
+
- lib/rack2aws.rb
|
79
|
+
- lib/rack2aws/config.rb
|
80
|
+
- lib/rack2aws/errors.rb
|
81
|
+
- lib/rack2aws/props_reader.rb
|
82
|
+
- lib/rack2aws/version.rb
|
83
|
+
homepage: https://amrfaissal.github.io/rack2aws
|
84
|
+
licenses:
|
85
|
+
- MIT
|
86
|
+
metadata: {}
|
87
|
+
post_install_message:
|
88
|
+
rdoc_options: []
|
89
|
+
require_paths:
|
90
|
+
- lib
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
requirements: []
|
102
|
+
rubyforge_project:
|
103
|
+
rubygems_version: 2.5.1
|
104
|
+
signing_key:
|
105
|
+
specification_version: 4
|
106
|
+
summary: Bridge from Rackspace Cloud Files to AWS S3
|
107
|
+
test_files: []
|