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