mistfiles 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/mistfiles.rb +16 -0
- data/lib/mistfiles/authentication.rb +59 -0
- data/lib/mistfiles/containers.rb +107 -0
- data/lib/mistfiles/objects.rb +69 -0
- data/lib/mistfiles/send_request.rb +102 -0
- metadata +93 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9f7f10178da79245cc77a6c42749c2a3a3f35057
|
4
|
+
data.tar.gz: 1c1e98703ea42f8bb5c6746f77e005305f341151
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 20a6f574ac05352f19a382020027a99972aedcb2bc84cb5b4a43de9a621ee0bf73fad2b48ffed4e243dbc4b66642444a336f072e25c7cc17b9526c3a290246db
|
7
|
+
data.tar.gz: 8e6a3432eb73de15f8d87cab4b6ab7e4e3e46ae4bdbbb9725b3568c7067841ef2eee644bc230886b9ed452496c3dd434968729bf37e12f5723019d4b6b204e3a
|
data/lib/mistfiles.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "mistfiles/send_request"
|
4
|
+
require "mistfiles/authentication"
|
5
|
+
require "mistfiles/objects"
|
6
|
+
require "mistfiles/containers"
|
7
|
+
|
8
|
+
class MistFiles
|
9
|
+
def initialize(opts={})
|
10
|
+
@username = opts[:username] || opts["username"]
|
11
|
+
@api_key = opts[:api_key] || opts["api_key"]
|
12
|
+
@region = (opts[:region] || opts["region"]).downcase.to_sym
|
13
|
+
@british = (opts[:british] || opts["british"]) || false
|
14
|
+
@internal = (opts[:internal] || opts["internal"]) || false
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "mistfiles"
|
4
|
+
require "net/https"
|
5
|
+
require "uri"
|
6
|
+
require "date"
|
7
|
+
require "digest/md5"
|
8
|
+
|
9
|
+
class MistFiles
|
10
|
+
AuthenticationError = Class.new(StandardError)
|
11
|
+
RACKSPACE_US_URL = "https://identity.api.rackspacecloud.com/"
|
12
|
+
RACKSPACE_UK_URL = "https://lon.identity.api.rackspacecloud.com/"
|
13
|
+
|
14
|
+
def authenticated?
|
15
|
+
@token && @token.length > 0 && @expires && @expires.to_time.to_i > Time.now.to_i
|
16
|
+
end
|
17
|
+
|
18
|
+
def authenticate!
|
19
|
+
json_body = {
|
20
|
+
:auth => {
|
21
|
+
"RAX-KSKEY:apiKeyCredentials" => {
|
22
|
+
:username => @username,
|
23
|
+
:apiKey => @api_key
|
24
|
+
}
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
28
|
+
res = send_request(
|
29
|
+
:host => @british ? RACKSPACE_UK_URL : RACKSPACE_US_URL,
|
30
|
+
:path => "/v2.0/tokens",
|
31
|
+
:method => :post,
|
32
|
+
:body_type => :json,
|
33
|
+
:params => json_body
|
34
|
+
)
|
35
|
+
|
36
|
+
raise AuthenticationError, "HTTP error #{res.code}" if res.code < 200 || res.code > 299
|
37
|
+
|
38
|
+
body = JSON.parse(res.body)
|
39
|
+
raise AuthenticationError, "#{self.class}: Invalid response data, something must be wrong." unless body.member?("access")
|
40
|
+
access = body["access"]
|
41
|
+
|
42
|
+
@token = access["token"]["id"]
|
43
|
+
@expires = DateTime.parse(access["token"]["expires"])
|
44
|
+
@user = access["user"]
|
45
|
+
@user[:id] = @user.delete "id"
|
46
|
+
@user[:username] = @user.delete "name"
|
47
|
+
@user[:default_region] = @user.delete("RAX-AUTH:defaultRegion").downcase.to_sym
|
48
|
+
|
49
|
+
catalog = access["serviceCatalog"].select { |service| RS_SERVICE_TYPES.include?(service["type"]) }.each_with_object({}) { |entry,ctlg| ctlg[entry["type"]] = entry["endpoints"] }
|
50
|
+
|
51
|
+
@cdn_catalog = Hash[catalog["rax:object-cdn"].map{ |endpt| [endpt["region"].downcase.to_sym, endpt["publicURL"]] }]
|
52
|
+
@files_catalog = Hash[catalog["object-store"].map{ |endpt| [endpt["region"].downcase.to_sym, endpt[@internal ? "internalURL" : "publicURL"]] }]
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
|
56
|
+
def auth_if_necessary!
|
57
|
+
authenticate! unless authenticated?
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
#!/usr/bin/env
|
2
|
+
|
3
|
+
require "mistfiles"
|
4
|
+
|
5
|
+
class MistFiles
|
6
|
+
def create_container(params={})
|
7
|
+
auth_if_necessary!
|
8
|
+
container = params[:container] || params["container"]
|
9
|
+
headers = params[:headers] || params["headers"]
|
10
|
+
send_request(
|
11
|
+
:host => @files_catalog[@region],
|
12
|
+
:path => "/#{container}",
|
13
|
+
:method => :put,
|
14
|
+
:params => params || {},
|
15
|
+
:headers => headers
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
def update_container(params={})
|
20
|
+
auth_if_necessary!
|
21
|
+
container = params[:container] || params["container"]
|
22
|
+
headers = params[:headers] || params["headers"]
|
23
|
+
send_request(
|
24
|
+
:host => @files_catalog[@region],
|
25
|
+
:path => "/#{container}",
|
26
|
+
:method => :post,
|
27
|
+
:params => params || {},
|
28
|
+
:headers => headers
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
def destroy_container(params={})
|
33
|
+
auth_if_necessary!
|
34
|
+
container = params[:container] || params["container"]
|
35
|
+
send_request(
|
36
|
+
:host => @files_catalog[@region],
|
37
|
+
:path => "/#{container}",
|
38
|
+
:method => :delete,
|
39
|
+
:params => params || {}
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
# list_containers(params={})
|
44
|
+
# :limit => the number of containers to return
|
45
|
+
# :marker => returns results after the container name specified here
|
46
|
+
# :end_marker => retuns results before the container name
|
47
|
+
def list_containers(params={})
|
48
|
+
auth_if_necessary!
|
49
|
+
send_request(
|
50
|
+
:host => @files_catalog[@region],
|
51
|
+
:method => :get,
|
52
|
+
:params => params || {}
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
# lists objects
|
57
|
+
# ALSO gets container metadata
|
58
|
+
def list_objects(params={})
|
59
|
+
auth_if_necessary!
|
60
|
+
|
61
|
+
container = (params[:container] || params["container"])
|
62
|
+
|
63
|
+
send_request(
|
64
|
+
:host => @files_catalog[@region],
|
65
|
+
:path => "/#{container}",
|
66
|
+
:method => :get,
|
67
|
+
:params => params || {}
|
68
|
+
)
|
69
|
+
end
|
70
|
+
|
71
|
+
# parameters
|
72
|
+
# :ttl => The ttl in seconds
|
73
|
+
# :enabled => A true or false value indicating the CDN status of the container
|
74
|
+
# :log_retention => Whether RackSpace should store CDN access logs in the container.
|
75
|
+
# IMPORTANT: if the log_retention parameter is passed, this request will not be able to
|
76
|
+
def cdn_settings=(params={})
|
77
|
+
auth_if_necessary!
|
78
|
+
container = params[:container] || params["container"]
|
79
|
+
ttl = (params[:ttl] || params["ttl"]).to_i.to_s
|
80
|
+
enabled = (params[:enabled] || params["enabled"]) ? "True" : "False"
|
81
|
+
log_retention = params[:log_retention] || params["log_retention"]
|
82
|
+
|
83
|
+
headers = {}
|
84
|
+
headers["X-Ttl"] = ttl unless ttl.nil? || ttl.empty?
|
85
|
+
headers["X-Cdn-Enabled"] = container unless container.nil? || container.empty?
|
86
|
+
headers["X-Log-Retention"] = log_retention if params.member?(:log_retention) || params.member?("log_retention")
|
87
|
+
|
88
|
+
method = headers.member?("X-Log-Retention") ? :post : :put
|
89
|
+
|
90
|
+
send_request(
|
91
|
+
:host => @cdn_catalog[@region],
|
92
|
+
:path => "/#{container}",
|
93
|
+
:method => method,
|
94
|
+
:headers => headers
|
95
|
+
)
|
96
|
+
end
|
97
|
+
|
98
|
+
def cdn_settings(params={})
|
99
|
+
auth_if_necessary!
|
100
|
+
container = params[:container] || params["container"]
|
101
|
+
send_request(
|
102
|
+
:host => @cdn_catalog[@region],
|
103
|
+
:path => "/#{container}",
|
104
|
+
:method => :head
|
105
|
+
)
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "mistfiles"
|
4
|
+
|
5
|
+
class MistFiles
|
6
|
+
def create_object(params={})
|
7
|
+
auth_if_necessary!
|
8
|
+
# Required parameters
|
9
|
+
data = params[:data] || params["data"]
|
10
|
+
container = params[:container] || params["container"]
|
11
|
+
object = params[:object] || params["object"]
|
12
|
+
|
13
|
+
# optional parameters
|
14
|
+
content_type = params[:content_type] || params["content_type"] # can be detected by Rackspace
|
15
|
+
delete_at = (params[:delete_at] || params["delete_at"]).to_i
|
16
|
+
delete_after = (params[:delete_after] || params["delete_after"]).to_i
|
17
|
+
name = (params[:name] || params["name"])
|
18
|
+
|
19
|
+
# TODO: Implement X-Copy-From
|
20
|
+
|
21
|
+
headers = {
|
22
|
+
"ETag" => Digest::MD5.digest(data)
|
23
|
+
}
|
24
|
+
|
25
|
+
# Either X-Detect-Content-Type or Content-Type is required
|
26
|
+
headers["X-Detect-Content-Type"] = "True" if content_type.nil?
|
27
|
+
headers["Content-Type"] = content_type unless content_type.nil?
|
28
|
+
headers["X-Object-Meta-name"] = name unless name.nil?
|
29
|
+
|
30
|
+
send_request(
|
31
|
+
:host => @files_catalog[@region],
|
32
|
+
:path => "/#{container}/#{object}",
|
33
|
+
:method => :put,
|
34
|
+
:body_type => :raw,
|
35
|
+
:params => data,
|
36
|
+
:headers => headers
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Create and Update are the same
|
41
|
+
# Rackspace doesn't have a patch endpoint here
|
42
|
+
alias_method :update_object, :create_object
|
43
|
+
|
44
|
+
def delete_object(params={})
|
45
|
+
auth_if_necessary!
|
46
|
+
container = params[:container] || params["container"]
|
47
|
+
object = params[:object] || params["object"]
|
48
|
+
send_request(
|
49
|
+
:host => @files_catalog[@region],
|
50
|
+
:path => "/#{container}/#{object}",
|
51
|
+
:method => :delete
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
55
|
+
def get_object(params={})
|
56
|
+
auth_if_necessary!
|
57
|
+
ontainer = params[:container] || params["container"]
|
58
|
+
object = params[:object] || params["object"]
|
59
|
+
headers = params[:headers] || params["headers"]
|
60
|
+
send_request(
|
61
|
+
:host => @files_catalog[@region],
|
62
|
+
:path => "/#{container}/#{object}",
|
63
|
+
:method => :get,
|
64
|
+
:params => params || {},
|
65
|
+
:headers => headers,
|
66
|
+
:parse_json => false
|
67
|
+
)
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "mistfiles"
|
4
|
+
require "net/https"
|
5
|
+
require "uri"
|
6
|
+
require "json"
|
7
|
+
|
8
|
+
class MistFilesResponse
|
9
|
+
attr_accessor :code, :headers, :body
|
10
|
+
|
11
|
+
# takes a Net::HTTPResponse
|
12
|
+
def initialize(opts={})
|
13
|
+
@res = opts[:response]
|
14
|
+
@json = opts[:parse_json]
|
15
|
+
@code = @res.code.to_i
|
16
|
+
@headers = Hash[@res.to_hash.map{ |k,v| [k,(v.first rescue v)] }]
|
17
|
+
@body = @res.body || ""
|
18
|
+
@body = JSON.parse @res.body if @json && !@body.empty?
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_rack
|
22
|
+
[@code, @headers, (@json ? @body.to_json : @body)]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class MistFiles
|
27
|
+
RS_SERVICE_TYPES = ["object-store", "rax:object-cdn"]
|
28
|
+
VERB_MAP = {
|
29
|
+
:get => Net::HTTP::Get,
|
30
|
+
:post => Net::HTTP::Post,
|
31
|
+
:put => Net::HTTP::Put,
|
32
|
+
:patch => Net::HTTP::Patch,
|
33
|
+
:delete => Net::HTTP::Delete,
|
34
|
+
:head => Net::HTTP::Head
|
35
|
+
}
|
36
|
+
DEFAULT_HTTP_HEADERS = {
|
37
|
+
"Accept" => "application/json",
|
38
|
+
"Content-Type" => "application/json"
|
39
|
+
}
|
40
|
+
|
41
|
+
def http(opts={})
|
42
|
+
@http_objs ||= {}
|
43
|
+
|
44
|
+
uri = opts[:uri] || opts["uri"]
|
45
|
+
http = @http_objs[uri.host]
|
46
|
+
return http unless http.nil?
|
47
|
+
|
48
|
+
http = Net::HTTP.new uri.host, uri.port
|
49
|
+
if uri.port == 443
|
50
|
+
http.use_ssl = true
|
51
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
52
|
+
end
|
53
|
+
@http_objs[uri.host] = http
|
54
|
+
http
|
55
|
+
end
|
56
|
+
|
57
|
+
def send_request(opts={})
|
58
|
+
parse_json = (opts[:parse_json] || opts["parse_json"]) == true
|
59
|
+
host = opts[:host] || opts["host"]
|
60
|
+
path = opts[:path] || opts["path"] || ""
|
61
|
+
method = (opts[:method] || opts["method"]).to_sym
|
62
|
+
params = opts[:params] || opts["params"] || {}
|
63
|
+
body = opts[:body] || opts["body"]
|
64
|
+
body_type = (opts[:body_type] || opts["body_type"] || :ignored).to_sym
|
65
|
+
headers = DEFAULT_HTTP_HEADERS.merge(opts[:headers] || opts["headers"] || {})
|
66
|
+
headers.merge!({ "X-Auth-Token" => @token }) unless @token.nil? || @token.empty?
|
67
|
+
|
68
|
+
uri = URI.parse host
|
69
|
+
path = uri.path + path
|
70
|
+
|
71
|
+
request =
|
72
|
+
case method
|
73
|
+
when :get
|
74
|
+
path << "?" + URI.encode_www_form(params) unless params.empty?
|
75
|
+
VERB_MAP[method].new path
|
76
|
+
else
|
77
|
+
request = VERB_MAP[method].new path
|
78
|
+
case body_type
|
79
|
+
when :json
|
80
|
+
request.body =
|
81
|
+
case params
|
82
|
+
when String
|
83
|
+
params
|
84
|
+
else
|
85
|
+
params.to_json
|
86
|
+
end
|
87
|
+
when :form_data
|
88
|
+
request.set_form_data params
|
89
|
+
when :raw
|
90
|
+
request.body = body
|
91
|
+
end
|
92
|
+
request
|
93
|
+
end
|
94
|
+
|
95
|
+
headers.each { |k,v| request.add_field k,v }
|
96
|
+
|
97
|
+
MistFilesResponse.new(
|
98
|
+
:response => http(:uri => uri).request(request),
|
99
|
+
:parse_json => parse_json
|
100
|
+
)
|
101
|
+
end
|
102
|
+
end
|
metadata
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mistfiles
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nathaniel Symer
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-08-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: net-http
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: net-https
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: uri
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: Mistfiles is a gem for people who don't like the 'Rails' way. Mistfiles
|
56
|
+
returns a Net::HTTP response instead of meaningless objects, and each method sends
|
57
|
+
only one HTTP request to Rackspace.
|
58
|
+
email:
|
59
|
+
- nate@natesymer.com
|
60
|
+
executables: []
|
61
|
+
extensions: []
|
62
|
+
extra_rdoc_files: []
|
63
|
+
files:
|
64
|
+
- lib/mistfiles.rb
|
65
|
+
- lib/mistfiles/authentication.rb
|
66
|
+
- lib/mistfiles/containers.rb
|
67
|
+
- lib/mistfiles/objects.rb
|
68
|
+
- lib/mistfiles/send_request.rb
|
69
|
+
homepage: https://github.com/ivytap/mistfiles
|
70
|
+
licenses:
|
71
|
+
- MIT
|
72
|
+
metadata: {}
|
73
|
+
post_install_message:
|
74
|
+
rdoc_options: []
|
75
|
+
require_paths:
|
76
|
+
- lib
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
requirements: []
|
88
|
+
rubyforge_project:
|
89
|
+
rubygems_version: 2.2.2
|
90
|
+
signing_key:
|
91
|
+
specification_version: 4
|
92
|
+
summary: A better Rackspace CloudFiles client.
|
93
|
+
test_files: []
|