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,56 @@
|
|
1
|
+
class Fastly
|
2
|
+
# :nodoc:
|
3
|
+
class AuthRequired < RuntimeError; end
|
4
|
+
# :nodoc:
|
5
|
+
class FullAuthRequired < Fastly::AuthRequired; end
|
6
|
+
# :nodoc:
|
7
|
+
class Error < RuntimeError; end
|
8
|
+
# :nodoc:
|
9
|
+
class Unauthorized < AuthRequired; end
|
10
|
+
|
11
|
+
module Fetcher # :nodoc: all
|
12
|
+
|
13
|
+
# Get the current Fastly::Client
|
14
|
+
def client(opts={})
|
15
|
+
opts[:base_url] ||= 'api.fastly.com'
|
16
|
+
opts[:base_port] ||= 80
|
17
|
+
@client ||= Fastly::Client.new(opts)
|
18
|
+
end
|
19
|
+
|
20
|
+
def list(klass, opts={})
|
21
|
+
raise Fastly::FullAuthRequired unless self.fully_authed?
|
22
|
+
list = client.get(klass.list_path, opts)
|
23
|
+
return [] if list.nil?
|
24
|
+
list.map { |hash| klass.new(hash, self) }
|
25
|
+
end
|
26
|
+
|
27
|
+
def get(klass, *args)
|
28
|
+
raise Fastly::FullAuthRequired unless self.fully_authed?
|
29
|
+
if [User, Customer].include?(klass) && args.empty?
|
30
|
+
hash = client.get("/current_#{klass.path}")
|
31
|
+
else
|
32
|
+
hash = client.get(klass.get_path(*args))
|
33
|
+
end
|
34
|
+
return nil if hash.nil?
|
35
|
+
return klass.new(hash, self)
|
36
|
+
end
|
37
|
+
|
38
|
+
def create(klass, opts)
|
39
|
+
raise Fastly::FullAuthRequired unless self.fully_authed?
|
40
|
+
hash = client.post(klass.post_path(opts),opts)
|
41
|
+
return klass.new(hash, self)
|
42
|
+
end
|
43
|
+
|
44
|
+
def update(klass, obj)
|
45
|
+
raise Fastly::FullAuthRequired unless self.fully_authed?
|
46
|
+
hash = client.put(klass.put_path(obj), obj.as_hash)
|
47
|
+
return klass.new(hash, self)
|
48
|
+
end
|
49
|
+
|
50
|
+
def delete(klass, obj)
|
51
|
+
raise Fastly::FullAuthRequired unless self.fully_authed?
|
52
|
+
return client.delete(klass.delete_path(obj))
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
class Fastly
|
2
|
+
# An invoice for a time period
|
3
|
+
class Invoice < Base
|
4
|
+
attr_accessor :service_id, :service_name, :start_time, :end_time, :total, :regions
|
5
|
+
|
6
|
+
##
|
7
|
+
# :attr: service_id
|
8
|
+
#
|
9
|
+
# The id of the service this invoice is for
|
10
|
+
#
|
11
|
+
|
12
|
+
##
|
13
|
+
# :attr: service_name
|
14
|
+
#
|
15
|
+
# The id of the service this invoice is for
|
16
|
+
#
|
17
|
+
|
18
|
+
##
|
19
|
+
# :attr: start_time
|
20
|
+
#
|
21
|
+
# The earliest date and time this invoice covers
|
22
|
+
#
|
23
|
+
|
24
|
+
##
|
25
|
+
# :attr: end_time
|
26
|
+
#
|
27
|
+
# The latest date and time this invoice covers
|
28
|
+
#
|
29
|
+
|
30
|
+
##
|
31
|
+
# :attr: total
|
32
|
+
#
|
33
|
+
# The total for this invoice in US dollars
|
34
|
+
#
|
35
|
+
|
36
|
+
##
|
37
|
+
# :attr: regions
|
38
|
+
#
|
39
|
+
# A hash reference with all the different regions and their subtotals
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def self.get_path(*args)
|
44
|
+
opts = args.size>0 ? args[0] : {}
|
45
|
+
url = "/billing"
|
46
|
+
if opts.has_key?(:service_id)
|
47
|
+
url += "/service/#{opts[:service_id]}"
|
48
|
+
end
|
49
|
+
if opts.has_key?(:year) && opts.has_key?(:month)
|
50
|
+
url += "/year/#{opts[:year]}/month/#{opts[:month]}"
|
51
|
+
end
|
52
|
+
url
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.list_path(*args)
|
56
|
+
get_path(*args)
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.post_path
|
60
|
+
raise "You can't POST to an invoice"
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.put_path
|
64
|
+
raise "You can't PUT to an invoice"
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.delete_path
|
68
|
+
raise "You can't DELETE to an invoice"
|
69
|
+
end
|
70
|
+
|
71
|
+
def save!
|
72
|
+
raise "You can't save an invoice"
|
73
|
+
end
|
74
|
+
|
75
|
+
def delete!
|
76
|
+
raise "You can't delete an invoice"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
# Return an array of Invoice objects representing invoices for all services.
|
82
|
+
#
|
83
|
+
# If a year and month are passed in returns the invoices for that whole month.
|
84
|
+
#
|
85
|
+
# Otherwise it returns the invoices for the current month so far.
|
86
|
+
def list_invoices(year=nil, month=nil)
|
87
|
+
opts = {}
|
88
|
+
unless year.nil? || month.nil?
|
89
|
+
opts[:year] = year
|
90
|
+
opts[:month] = month
|
91
|
+
end
|
92
|
+
list(Fastly::Invoice, opts)
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
data/lib/fastly/match.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
class Fastly
|
2
|
+
# An object that allows matching on requests and dispatching to different methods
|
3
|
+
class Match < BelongsToServiceAndVersion
|
4
|
+
attr_accessor :service_id, :name, :comment, :pattern, :priority, :on_recv, :on_lookup, :on_fetch, :on_deliver, :on_miss, :on_hit
|
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 name of this match.
|
22
|
+
#
|
23
|
+
|
24
|
+
##
|
25
|
+
# :attr: pattern
|
26
|
+
#
|
27
|
+
# The matching pattern.
|
28
|
+
#
|
29
|
+
|
30
|
+
##
|
31
|
+
# :attr: on_recv
|
32
|
+
#
|
33
|
+
# What VCL action to execute before we lookup the object.
|
34
|
+
#
|
35
|
+
|
36
|
+
##
|
37
|
+
# :attr: on_lookup
|
38
|
+
#
|
39
|
+
# What VCL action to execute during a lookup.
|
40
|
+
#
|
41
|
+
|
42
|
+
##
|
43
|
+
# :attr: on_fetch
|
44
|
+
#
|
45
|
+
# What to execute after we have the header.
|
46
|
+
#
|
47
|
+
|
48
|
+
##
|
49
|
+
# :attr: on_miss
|
50
|
+
#
|
51
|
+
# What to execute on a cache miss
|
52
|
+
#
|
53
|
+
|
54
|
+
##
|
55
|
+
# :attr: on_hit
|
56
|
+
#
|
57
|
+
# What to execute on a cache hit.
|
58
|
+
#
|
59
|
+
|
60
|
+
##
|
61
|
+
# :attr: on_deliver
|
62
|
+
#
|
63
|
+
# What to execute just before delivering the object.
|
64
|
+
#
|
65
|
+
|
66
|
+
##
|
67
|
+
# :attr: priority
|
68
|
+
#
|
69
|
+
# The ordering of the match object
|
70
|
+
#
|
71
|
+
|
72
|
+
##
|
73
|
+
# :attr: comment
|
74
|
+
#
|
75
|
+
# a free form comment field
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class Fastly
|
2
|
+
# A logical group of directors - for example the asset server directors from all your DCNs
|
3
|
+
class Origin < 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
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
class Fastly
|
2
|
+
# Represents something you want to serve - this can be, for example, a whole web site, a Wordpress site, or just your image servers
|
3
|
+
class Service < Base
|
4
|
+
attr_accessor :id, :customer, :name, :comment
|
5
|
+
@versions = []
|
6
|
+
|
7
|
+
##
|
8
|
+
# :attr: id
|
9
|
+
#
|
10
|
+
# The id of the service
|
11
|
+
#
|
12
|
+
|
13
|
+
##
|
14
|
+
# :attr: customer
|
15
|
+
#
|
16
|
+
# The id of the customer this belongs to
|
17
|
+
#
|
18
|
+
|
19
|
+
##
|
20
|
+
# :attr: name
|
21
|
+
#
|
22
|
+
# The name of this service
|
23
|
+
#
|
24
|
+
|
25
|
+
##
|
26
|
+
# :attr: comment
|
27
|
+
#
|
28
|
+
# a free form comment field
|
29
|
+
|
30
|
+
|
31
|
+
##
|
32
|
+
#
|
33
|
+
# Get a hash of stats from different data centers.
|
34
|
+
#
|
35
|
+
# Type can be one of
|
36
|
+
# * minutely
|
37
|
+
# * hourly
|
38
|
+
# * daily
|
39
|
+
# * all
|
40
|
+
def stats(type=:all)
|
41
|
+
raise Fastly::FullAuthRequired unless fetcher.fully_authed?
|
42
|
+
raise Fastly::Error "Unknown stats type #{type}" unless [:minutely,:hourly,:daily,:all].include?(type.to_sym)
|
43
|
+
hash = fetcher.client.get(Fastly::Service.get_path(self.id)+"/stats/#{type}")
|
44
|
+
return hash
|
45
|
+
end
|
46
|
+
|
47
|
+
# Return a Invoice object representing the invoice for this service
|
48
|
+
#
|
49
|
+
# If a year and month are passed in returns the invoice for that whole month.
|
50
|
+
#
|
51
|
+
# Otherwise it returns the invoice for the current month so far.
|
52
|
+
def invoice(year=nil, month=nil)
|
53
|
+
raise Fastly::FullAuthRequired unless fetcher.fully_authed?
|
54
|
+
opts = { :service_id => self.id }
|
55
|
+
unless year.nil? || month.nil?
|
56
|
+
opts[:year] = year
|
57
|
+
opts[:month] = month
|
58
|
+
end
|
59
|
+
fetcher.get(Fastly::Invoice, opts)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Purge all assets from this service.
|
63
|
+
def purge_all
|
64
|
+
raise Fastly::AuthRequired unless self.authed?
|
65
|
+
res = client.put(get_path(self.id)+"/purge_all")
|
66
|
+
end
|
67
|
+
|
68
|
+
# Set all the versions that this service has had.
|
69
|
+
def versions=(versions)
|
70
|
+
@versions = versions
|
71
|
+
end
|
72
|
+
|
73
|
+
# Get a sorted array of all the versions that this service has had.
|
74
|
+
def versions
|
75
|
+
raise Fastly::FullAuthRequired unless fetcher.fully_authed?
|
76
|
+
versions = []
|
77
|
+
@versions.each_pair { |number, version|
|
78
|
+
versions.push Fastly::Version.new({ :number => number, :service_id => self.id, :comment => version['comment'] || "" }, fetcher)
|
79
|
+
}
|
80
|
+
versions.sort {|a,b| a.number.to_i <=> b.number.to_i }
|
81
|
+
end
|
82
|
+
|
83
|
+
# Get an individual Version object. By default returns the latest version
|
84
|
+
def version(number=-1)
|
85
|
+
raise Fastly::FullAuthRequired unless fetcher.fully_authed?
|
86
|
+
versions[number]
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
# Get a list of all the services that the current customer has.
|
92
|
+
def list_services(opts={})
|
93
|
+
list(Fastly::Service, opts)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Search all the services that the current customer has.
|
97
|
+
#
|
98
|
+
# In general you'll want to do
|
99
|
+
#
|
100
|
+
# services = fastly.search_services(:name => name)
|
101
|
+
#
|
102
|
+
# or
|
103
|
+
#
|
104
|
+
# service = fastly.search_services(:name => name, :version => number)
|
105
|
+
def search_services(opts)
|
106
|
+
raise Fastly::FullAuthRequired unless self.fully_authed?
|
107
|
+
klass = Fastly::Service
|
108
|
+
hash = client.get(klass.post_path+"/search", opts)
|
109
|
+
return nil if hash.nil?
|
110
|
+
klass.new(hash, self)
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
class Fastly
|
2
|
+
# Represent arbitary key value settings for a given Version
|
3
|
+
class Settings < Base
|
4
|
+
attr_accessor :service_id, :version, :settings
|
5
|
+
##
|
6
|
+
# :attr: service_id
|
7
|
+
#
|
8
|
+
# The id of the service this belongs to.
|
9
|
+
#
|
10
|
+
|
11
|
+
##
|
12
|
+
# :attr: version
|
13
|
+
#
|
14
|
+
# The number of the version this belongs to.
|
15
|
+
#
|
16
|
+
|
17
|
+
##
|
18
|
+
# :attr: service
|
19
|
+
#
|
20
|
+
# Return a hash containing key/value pairs of settings
|
21
|
+
#
|
22
|
+
|
23
|
+
|
24
|
+
# :nodoc:
|
25
|
+
def self.get_path(service, number)
|
26
|
+
"/service/#{service}/version/#{number}/settings"
|
27
|
+
end
|
28
|
+
|
29
|
+
# :nodoc:
|
30
|
+
def self.put_path(obj)
|
31
|
+
get_path(obj.service_id, obj.version)
|
32
|
+
end
|
33
|
+
|
34
|
+
# :nodoc:
|
35
|
+
def self.list_path(opts={})
|
36
|
+
raise "You can't list settings"
|
37
|
+
end
|
38
|
+
|
39
|
+
# :nodoc:
|
40
|
+
def self.post_path
|
41
|
+
raise "You can't POST to an invoice"
|
42
|
+
end
|
43
|
+
|
44
|
+
# :nodoc:
|
45
|
+
def self.delete_path
|
46
|
+
raise "You can't DELETE to an invoice"
|
47
|
+
end
|
48
|
+
|
49
|
+
# :nodoc:
|
50
|
+
def delete!
|
51
|
+
raise "You can't delete an invoice"
|
52
|
+
end
|
53
|
+
|
54
|
+
# :nodoc:
|
55
|
+
def as_hash
|
56
|
+
settings
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Get the Settings object for the specified Version
|
61
|
+
def get_settings(service, number)
|
62
|
+
get(Fastly::Settings, service, number)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Update the Settings object for the specified Version
|
66
|
+
def update_settings(opts={})
|
67
|
+
update(Fastly::Settings, opts)
|
68
|
+
end
|
69
|
+
end
|
data/lib/fastly/user.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
class Fastly
|
2
|
+
# A representation of a User in Fastly
|
3
|
+
class User < Base
|
4
|
+
attr_accessor :id, :name, :login, :customer_id, :role, :password
|
5
|
+
##
|
6
|
+
# :attr: id
|
7
|
+
#
|
8
|
+
# The id of this user
|
9
|
+
#
|
10
|
+
|
11
|
+
##
|
12
|
+
# :attr: name
|
13
|
+
#
|
14
|
+
# The name of this user
|
15
|
+
#
|
16
|
+
|
17
|
+
##
|
18
|
+
# :attr: customer_id
|
19
|
+
#
|
20
|
+
# The id of the customer this user belongs to
|
21
|
+
#
|
22
|
+
|
23
|
+
##
|
24
|
+
# :attr: role
|
25
|
+
#
|
26
|
+
# The role this user has (one of admin, owner, superuser, user, engineer, billing)
|
27
|
+
|
28
|
+
|
29
|
+
# Get the Customer object this user belongs to
|
30
|
+
def customer
|
31
|
+
@customer ||= fetcher.get(Customer, self.customer_id)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Whether or not this User is the owner of the Customer they belong to
|
35
|
+
def owner?
|
36
|
+
customer.owner_id == id
|
37
|
+
end
|
38
|
+
|
39
|
+
# :nodoc:
|
40
|
+
PRIORITIES = {
|
41
|
+
:admin => 1,
|
42
|
+
:owner => 10,
|
43
|
+
:superuser => 10,
|
44
|
+
:user => 20,
|
45
|
+
:engineer => 30,
|
46
|
+
:billing => 30,
|
47
|
+
}
|
48
|
+
|
49
|
+
# Does this User have sufficient permissions to perform the given role
|
50
|
+
def can_do?(test_role)
|
51
|
+
test_priority = PRIORITIES[test_role.to_sym] || 1000
|
52
|
+
my_priority = PRIORITIES[self.role.to_sym] || 1000
|
53
|
+
|
54
|
+
if test_priority == my_priority
|
55
|
+
test_role.to_s == :owner ? owner? : test_role.to_sym == self.role.to_sym
|
56
|
+
else
|
57
|
+
my_priority < test_priority
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|