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 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: []