mistfiles 0.0.1
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/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: []
|