fastly 0.5
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.
- data/.gitignore +6 -0
- data/Gemfile +4 -0
- data/README.md +59 -0
- data/Rakefile +1 -0
- data/bin/fastly_upload_vcl +67 -0
- data/fastly.gemspec +23 -0
- data/lib/fastly.rb +357 -0
- data/lib/fastly/backend.rb +99 -0
- data/lib/fastly/base.rb +62 -0
- data/lib/fastly/belongs_to_service_and_version.rb +42 -0
- data/lib/fastly/client.rb +160 -0
- data/lib/fastly/customer.rb +26 -0
- data/lib/fastly/director.rb +47 -0
- data/lib/fastly/domain.rb +29 -0
- data/lib/fastly/fetcher.rb +56 -0
- data/lib/fastly/invoice.rb +95 -0
- data/lib/fastly/match.rb +77 -0
- data/lib/fastly/origin.rb +23 -0
- data/lib/fastly/service.rb +112 -0
- data/lib/fastly/settings.rb +69 -0
- data/lib/fastly/user.rb +62 -0
- data/lib/fastly/vcl.rb +28 -0
- data/lib/fastly/version.rb +148 -0
- data/test/admin_test.rb +41 -0
- data/test/api_key_test.rb +52 -0
- data/test/common.rb +155 -0
- data/test/full_login_test.rb +98 -0
- data/test/helper.rb +24 -0
- metadata +157 -0
@@ -0,0 +1,99 @@
|
|
1
|
+
class Fastly
|
2
|
+
# An individual host you want to serve assets off
|
3
|
+
class Backend < BelongsToServiceAndVersion
|
4
|
+
attr_accessor :service_id, :name, :address, :ipv4, :ipv6, :hostname, :use_ssl, :client_cert, :port,
|
5
|
+
:connect_timeout, :first_byte_timeout, :between_bytes_timeout, :error_threshold, :max_conn, :weight, :comment
|
6
|
+
|
7
|
+
##
|
8
|
+
# :attr: service_id
|
9
|
+
#
|
10
|
+
# The id of the service this belongs to.
|
11
|
+
#
|
12
|
+
|
13
|
+
##
|
14
|
+
# :attr: version
|
15
|
+
#
|
16
|
+
# The number of the version this belongs to.
|
17
|
+
#
|
18
|
+
|
19
|
+
##
|
20
|
+
# :attr: name
|
21
|
+
#
|
22
|
+
# The name of this backend.
|
23
|
+
#
|
24
|
+
|
25
|
+
##
|
26
|
+
# :attr: address
|
27
|
+
#
|
28
|
+
# A magic field - will automagically be set to whichever of ipv4, ipv6 or hostname is currently set.
|
29
|
+
#
|
30
|
+
# Conversely if you set the address field then the correct field from ipv4, ipv6 or hostname will be set.
|
31
|
+
#
|
32
|
+
|
33
|
+
##
|
34
|
+
# :attr: ipv4
|
35
|
+
#
|
36
|
+
# the ipv4 address of the host to serve assets (this, hostname or ipv6 must be set)
|
37
|
+
#
|
38
|
+
|
39
|
+
##
|
40
|
+
# :attr: ipv6
|
41
|
+
#
|
42
|
+
# the ipv6 address of the host to serve assets (this, hostname or ipv4 must be set)
|
43
|
+
#
|
44
|
+
|
45
|
+
##
|
46
|
+
# :attr: hostname
|
47
|
+
#
|
48
|
+
# the hostname to serve assets from (this, ipv4 or ipv6 must be set)
|
49
|
+
#
|
50
|
+
|
51
|
+
##
|
52
|
+
# :attr: port
|
53
|
+
#
|
54
|
+
# the port to connect to (default 80)
|
55
|
+
#
|
56
|
+
|
57
|
+
##
|
58
|
+
# :attr: use_ssl
|
59
|
+
#
|
60
|
+
# whether to use ssl to get to the backend (default 0 i.e false)
|
61
|
+
#
|
62
|
+
|
63
|
+
##
|
64
|
+
# :attr: connect_timeout
|
65
|
+
#
|
66
|
+
# how long in milliseconds to wait for a connect before declaring the backend out of rotation (default 1,000)
|
67
|
+
#
|
68
|
+
|
69
|
+
##
|
70
|
+
# :attr: first_byte_timeout
|
71
|
+
#
|
72
|
+
# how long in milliseconds to wait for the first bytes before declaring the host out of rotation (default 15,000)
|
73
|
+
#
|
74
|
+
|
75
|
+
##
|
76
|
+
# :attr: between_bytes_timeout
|
77
|
+
#
|
78
|
+
# how long in milliseconds to wait between bytes before declaring the backend out of rotation (default 10,000)
|
79
|
+
#
|
80
|
+
|
81
|
+
##
|
82
|
+
# :attr: error_threshold
|
83
|
+
#
|
84
|
+
# how many errors before declaring the backend out of rotation (default 0, 0 meaning turned off)
|
85
|
+
#
|
86
|
+
|
87
|
+
##
|
88
|
+
# :attr: max_conn
|
89
|
+
#
|
90
|
+
# the maximum number of connections to this backend (default 20)
|
91
|
+
#
|
92
|
+
|
93
|
+
##
|
94
|
+
# :attr: weight
|
95
|
+
#
|
96
|
+
# the weight assigned to this backend (default 100)
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
data/lib/fastly/base.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
|
2
|
+
class Fastly
|
3
|
+
# Base class for all Fastly objects
|
4
|
+
class Base # :nodoc: all
|
5
|
+
attr_accessor :fetcher
|
6
|
+
|
7
|
+
def initialize(opts, fetcher)
|
8
|
+
@keys = []
|
9
|
+
opts.each do |key,val|
|
10
|
+
next unless self.respond_to? "#{key}="
|
11
|
+
self.send("#{key}=", val)
|
12
|
+
@keys.push(key)
|
13
|
+
end
|
14
|
+
self.fetcher = fetcher
|
15
|
+
end
|
16
|
+
|
17
|
+
# Save this object
|
18
|
+
def save!
|
19
|
+
fetcher.update(self.class, self)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Delete this object
|
23
|
+
def delete!
|
24
|
+
fetcher.delete(self.class, self)
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# :nodoc:
|
29
|
+
def as_hash
|
30
|
+
ret = {}
|
31
|
+
@keys.each do |key|
|
32
|
+
ret[key] = self.send("#{key}") unless key =~ /^_/;
|
33
|
+
end
|
34
|
+
ret
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.path
|
38
|
+
self.to_s.downcase.split("::")[-1]
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.get_path(id)
|
42
|
+
"/#{path}/#{id}"
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.post_path(opts={})
|
46
|
+
"/#{path}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.list_path(opts={})
|
50
|
+
post_path(opts)
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.put_path(obj)
|
54
|
+
get_path(obj.id)
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.delete_path(obj)
|
58
|
+
put_path(obj)
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class Fastly
|
2
|
+
class BelongsToServiceAndVersion < Base
|
3
|
+
# Return the Service object this belongs to
|
4
|
+
def service
|
5
|
+
@service ||= fetcher.get(Fastly::Service, service_id)
|
6
|
+
end
|
7
|
+
|
8
|
+
# Set the Version object this belongs to
|
9
|
+
def version=(version)
|
10
|
+
@version = version
|
11
|
+
end
|
12
|
+
|
13
|
+
# Get the Version object this belongs to
|
14
|
+
def version
|
15
|
+
@version_obj ||= fetcher.get(Fastly::Version, service_id, version_number)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Get the number of the Version this belongs to
|
19
|
+
def version_number
|
20
|
+
@version
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def self.get_path(service, version, name)
|
26
|
+
"/service/#{service}/version/#{version}/#{path}/#{name}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.post_path(opts)
|
30
|
+
"/service/#{opts[:service_id]}/version/#{opts[:version]}/#{path}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.put_path(obj)
|
34
|
+
get_path(obj.service_id, obj.version_number,obj.name)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.delete_path(obj)
|
38
|
+
put_path(obj)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'net/https'
|
3
|
+
require 'json'
|
4
|
+
require 'cgi'
|
5
|
+
require 'pp'
|
6
|
+
require 'uri'
|
7
|
+
|
8
|
+
class Fastly
|
9
|
+
# The UserAgent to communicate with the API
|
10
|
+
class Client #:nodoc: all
|
11
|
+
begin
|
12
|
+
require 'curb-fu'
|
13
|
+
CURB_FU=true
|
14
|
+
rescue LoadError
|
15
|
+
CURB_FU=false
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_accessor :http, :api_key, :user, :password, :cookie
|
19
|
+
|
20
|
+
def initialize(opts)
|
21
|
+
[:api_key, :user, :password].each do |key|
|
22
|
+
self.send("#{key}=", opts[key]) if opts.has_key?(key)
|
23
|
+
end
|
24
|
+
base = opts[:base_url] || "https://api.fastly.com"
|
25
|
+
port = opts[:base_port] || 80
|
26
|
+
uri = URI.parse(base)
|
27
|
+
scheme = uri.scheme
|
28
|
+
host = uri.host
|
29
|
+
curb = opts.has_key?(:use_curb) ? !!opts[:use_curb] && CURB_FU : CURB_FU
|
30
|
+
self.http = curb ? Fastly::Client::Curl.new(host, port) : Net::HTTP.new(host, port)
|
31
|
+
self.http.use_ssl = (scheme == "https")
|
32
|
+
return self unless fully_authed?
|
33
|
+
|
34
|
+
# If we're fully authed (i.e username and password ) then we need to log in
|
35
|
+
resp = self.http.post('/login', make_params(:user => user, :password => password))
|
36
|
+
raise Fastly::Unauthorized unless resp.success?
|
37
|
+
self.cookie = resp['set-cookie']
|
38
|
+
content = JSON.parse(resp.body)
|
39
|
+
#return self, content['user'], content['content']
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
def authed?
|
45
|
+
!api_key.nil? || fully_authed?
|
46
|
+
end
|
47
|
+
|
48
|
+
# Some methods require full username and password rather than just auth token
|
49
|
+
def fully_authed?
|
50
|
+
!(user.nil? || password.nil?)
|
51
|
+
end
|
52
|
+
|
53
|
+
def get(path, params={})
|
54
|
+
path += "?"+make_params(params) unless params.empty?
|
55
|
+
resp = self.http.get(path, headers)
|
56
|
+
return nil if 404 == resp.status
|
57
|
+
raise Fastly::Error, resp.message unless resp.success?
|
58
|
+
JSON.parse(resp.body)
|
59
|
+
end
|
60
|
+
|
61
|
+
def post(path, params={})
|
62
|
+
post_and_put(:post, path, params)
|
63
|
+
end
|
64
|
+
|
65
|
+
def put(path, params={})
|
66
|
+
post_and_put(:put, path, params)
|
67
|
+
end
|
68
|
+
|
69
|
+
def delete(path)
|
70
|
+
resp = self.http.delete(path, headers)
|
71
|
+
return resp.success?
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def post_and_put(method, path, params={})
|
77
|
+
query = make_params(params)
|
78
|
+
resp = self.http.send(method, path, query, headers.merge( 'Content-Type' => "application/x-www-form-urlencoded"))
|
79
|
+
raise Fastly::Error, resp.message unless resp.success?
|
80
|
+
JSON.parse(resp.body)
|
81
|
+
end
|
82
|
+
|
83
|
+
def headers
|
84
|
+
(fully_authed? ? { 'Cookie' => cookie } : { 'X-Fastly-Key' => api_key }).merge( 'Content-Accept' => 'application/json')
|
85
|
+
end
|
86
|
+
|
87
|
+
def make_params(params)
|
88
|
+
params.map { |key,val|
|
89
|
+
next if val.nil?
|
90
|
+
unless val.is_a?(Hash)
|
91
|
+
"#{CGI.escape(key.to_s)}=#{CGI.escape(val.to_s)}"
|
92
|
+
else
|
93
|
+
val.map { |sub_key, sub_val|
|
94
|
+
new_key = "#{key}[#{sub_key}]"
|
95
|
+
"#{CGI.escape(new_key)}=#{CGI.escape(sub_val.to_s)}"
|
96
|
+
}
|
97
|
+
end
|
98
|
+
}.flatten.delete_if { |v| v.nil? }.join("&")
|
99
|
+
end
|
100
|
+
|
101
|
+
# :nodoc: all
|
102
|
+
class Curl
|
103
|
+
attr_accessor :host, :port, :protocol
|
104
|
+
|
105
|
+
def initialize(host, port=80)
|
106
|
+
self.host = host
|
107
|
+
self.port = port
|
108
|
+
self.protocol = 'https'
|
109
|
+
end
|
110
|
+
|
111
|
+
def get(path, headers={})
|
112
|
+
CurbFu.get({ :host => host, :port => port, :path => path, :headers => headers, :protocol => protocol })
|
113
|
+
end
|
114
|
+
|
115
|
+
def post(path, params, headers={})
|
116
|
+
CurbFu.post({ :host => host, :port => port, :path => path, :headers => headers, :protocol => protocol }, params)
|
117
|
+
end
|
118
|
+
|
119
|
+
def put(path, params, headers={})
|
120
|
+
CurbFu.put({ :host => host, :port => port, :path => path, :headers => headers, :params => params, :protocol => protocol }, params)
|
121
|
+
end
|
122
|
+
|
123
|
+
def delete(path, headers={})
|
124
|
+
CurbFu.delete({ :host => host, :port => port, :path => path, :headers => headers, :protocol => protocol })
|
125
|
+
end
|
126
|
+
|
127
|
+
def use_ssl=(ssl)
|
128
|
+
self.protocol = ssl ? 'https' : 'http'
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# :nodoc: all
|
135
|
+
class Net::HTTPResponse
|
136
|
+
def success?
|
137
|
+
return Net::HTTPSuccess === self
|
138
|
+
end
|
139
|
+
|
140
|
+
def status
|
141
|
+
return self.code.to_i
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
|
147
|
+
# :nodoc: all
|
148
|
+
class CurbFu::Response::Base
|
149
|
+
def get_fields(key)
|
150
|
+
if ( match = @headers.find{|k,v| k.downcase == key.downcase} )
|
151
|
+
[match.last].flatten
|
152
|
+
else
|
153
|
+
[]
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def [](key)
|
158
|
+
get_fields(key).last
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class Fastly
|
2
|
+
# A Customer account
|
3
|
+
class Customer < Base
|
4
|
+
attr_accessor :id, :name, :owner_id
|
5
|
+
|
6
|
+
##
|
7
|
+
# :attr: id
|
8
|
+
#
|
9
|
+
# The id of this customer
|
10
|
+
|
11
|
+
##
|
12
|
+
# :attr: name
|
13
|
+
#
|
14
|
+
# The name of this customer
|
15
|
+
|
16
|
+
##
|
17
|
+
# :attr: owner_id
|
18
|
+
#
|
19
|
+
# The id of the user that owns this customer
|
20
|
+
|
21
|
+
# Return a user object representing the owner of this Customer
|
22
|
+
def owner
|
23
|
+
self.fetcher.get(User,self.owner_id)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class Fastly
|
2
|
+
# A logical collection of backends - for example all the asset servers in one data center
|
3
|
+
class Director < BelongsToServiceAndVersion
|
4
|
+
attr_accessor :service_id, :name, :type, :comment, :retries, :capacity, :quorom
|
5
|
+
|
6
|
+
##
|
7
|
+
# :attr: service
|
8
|
+
#
|
9
|
+
# The id of the service this belongs to.
|
10
|
+
#
|
11
|
+
|
12
|
+
##
|
13
|
+
# :attr: version
|
14
|
+
#
|
15
|
+
# The number of the version this belongs to.
|
16
|
+
#
|
17
|
+
|
18
|
+
##
|
19
|
+
# :attr: name
|
20
|
+
#
|
21
|
+
# The domain name of this domain
|
22
|
+
#
|
23
|
+
|
24
|
+
##
|
25
|
+
# :attr: type
|
26
|
+
#
|
27
|
+
# what kind of Load Balancer group (currently always 1 meaning random)
|
28
|
+
#
|
29
|
+
|
30
|
+
##
|
31
|
+
# :attr: retries
|
32
|
+
#
|
33
|
+
# how many backends to search if it fails (default 5)
|
34
|
+
#
|
35
|
+
|
36
|
+
##
|
37
|
+
# :attr: quorum
|
38
|
+
#
|
39
|
+
# the percentage of capacity that needs to be up for a director to be considered up (default 75)
|
40
|
+
#
|
41
|
+
|
42
|
+
##
|
43
|
+
# :attr: comment
|
44
|
+
#
|
45
|
+
# a free form comment field
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class Fastly
|
2
|
+
# A domain name you want to map to a service
|
3
|
+
class Domain < BelongsToServiceAndVersion
|
4
|
+
attr_accessor :service_id, :name, :comment
|
5
|
+
|
6
|
+
##
|
7
|
+
# :attr: service_id
|
8
|
+
#
|
9
|
+
# The id of the service this belongs to.
|
10
|
+
#
|
11
|
+
|
12
|
+
##
|
13
|
+
# :attr: version
|
14
|
+
#
|
15
|
+
# The number of the version this belongs to.
|
16
|
+
#
|
17
|
+
|
18
|
+
##
|
19
|
+
# :attr: name
|
20
|
+
#
|
21
|
+
# The domain name of this domain
|
22
|
+
#
|
23
|
+
|
24
|
+
##
|
25
|
+
# :attr: comment
|
26
|
+
#
|
27
|
+
# a free form comment field
|
28
|
+
end
|
29
|
+
end
|