mistfiles 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: []