netlify 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +40 -0
- data/LICENSE.txt +22 -0
- data/README.md +423 -0
- data/Rakefile +7 -0
- data/lib/netlify.rb +17 -0
- data/lib/netlify/access_token.rb +5 -0
- data/lib/netlify/access_tokens.rb +7 -0
- data/lib/netlify/client.rb +113 -0
- data/lib/netlify/collection_proxy.rb +48 -0
- data/lib/netlify/deploy.rb +58 -0
- data/lib/netlify/deploys.rb +43 -0
- data/lib/netlify/dns_record.rb +5 -0
- data/lib/netlify/dns_records.rb +7 -0
- data/lib/netlify/dns_zone.rb +11 -0
- data/lib/netlify/dns_zones.rb +11 -0
- data/lib/netlify/file.rb +5 -0
- data/lib/netlify/files.rb +7 -0
- data/lib/netlify/form.rb +9 -0
- data/lib/netlify/forms.rb +7 -0
- data/lib/netlify/model.rb +65 -0
- data/lib/netlify/multipass.rb +70 -0
- data/lib/netlify/site.rb +69 -0
- data/lib/netlify/sites.rb +18 -0
- data/lib/netlify/snippet.rb +5 -0
- data/lib/netlify/snippets.rb +7 -0
- data/lib/netlify/submission.rb +6 -0
- data/lib/netlify/submissions.rb +7 -0
- data/lib/netlify/user.rb +17 -0
- data/lib/netlify/users.rb +7 -0
- data/lib/netlify/version.rb +3 -0
- data/test/client_test.rb +77 -0
- data/test/files/site-dir.zip +0 -0
- data/test/files/site-dir/index.html +5 -0
- data/test/multipass_test.rb +14 -0
- data/test/sites_test.rb +66 -0
- data/test/test_helper.rb +6 -0
- metadata +156 -0
data/Rakefile
ADDED
data/lib/netlify.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require "json"
|
2
|
+
require "netlify/version"
|
3
|
+
require "netlify/client"
|
4
|
+
require "netlify/collection_proxy"
|
5
|
+
require "netlify/model"
|
6
|
+
require "netlify/sites"
|
7
|
+
require "netlify/forms"
|
8
|
+
require "netlify/submissions"
|
9
|
+
require "netlify/files"
|
10
|
+
require "netlify/snippets"
|
11
|
+
require "netlify/users"
|
12
|
+
require "netlify/deploys"
|
13
|
+
require "netlify/dns_zones"
|
14
|
+
require "netlify/access_tokens"
|
15
|
+
require "netlify/multipass"
|
16
|
+
|
17
|
+
module Netlify; end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'oauth2'
|
2
|
+
|
3
|
+
module Netlify
|
4
|
+
class Client
|
5
|
+
ENDPOINT = ENV['OAUTH_CLIENT_API_URL'] || 'https://api.netlify.com'
|
6
|
+
API_VERSION = "v1"
|
7
|
+
RETRIES = 3
|
8
|
+
|
9
|
+
class NetlifyError < StandardError; end
|
10
|
+
class NotFoundError < NetlifyError; end
|
11
|
+
class ConnectionError < NetlifyError; end
|
12
|
+
class InternalServerError < NetlifyError; end
|
13
|
+
class AuthenticationError < NetlifyError; end
|
14
|
+
|
15
|
+
attr_accessor :client_id, :client_secret, :oauth, :access_token, :endpoint
|
16
|
+
|
17
|
+
def initialize(options)
|
18
|
+
self.client_id = options[:client_id]
|
19
|
+
self.client_secret = options[:client_secret]
|
20
|
+
self.access_token = options[:access_token]
|
21
|
+
self.endpoint = options[:endpoint] || ENDPOINT
|
22
|
+
self.oauth = OAuth2::Client.new(client_id, client_secret, :site => endpoint, :connection_build => lambda {|f|
|
23
|
+
f.request :multipart
|
24
|
+
f.request :url_encoded
|
25
|
+
f.adapter :net_http
|
26
|
+
})
|
27
|
+
end
|
28
|
+
|
29
|
+
def authorize_url(options)
|
30
|
+
oauth.auth_code.authorize_url(options)
|
31
|
+
end
|
32
|
+
|
33
|
+
def authorize_from_code!(authorization_code, options)
|
34
|
+
@oauth_token = oauth.auth_code.get_token(authorization_code, options)
|
35
|
+
self.access_token = oauth_token.token
|
36
|
+
end
|
37
|
+
|
38
|
+
def authorize_from_credentials!
|
39
|
+
@oauth_token = oauth.client_credentials.get_token
|
40
|
+
self.access_token = oauth_token.token
|
41
|
+
end
|
42
|
+
|
43
|
+
def sites
|
44
|
+
Sites.new(self)
|
45
|
+
end
|
46
|
+
|
47
|
+
def forms
|
48
|
+
Forms.new(self)
|
49
|
+
end
|
50
|
+
|
51
|
+
def submissions
|
52
|
+
Submissions.new(self)
|
53
|
+
end
|
54
|
+
|
55
|
+
def users
|
56
|
+
Users.new(self)
|
57
|
+
end
|
58
|
+
|
59
|
+
def dns_zones
|
60
|
+
DnsZones.new(self)
|
61
|
+
end
|
62
|
+
|
63
|
+
def access_tokens
|
64
|
+
AccessTokens.new(self)
|
65
|
+
end
|
66
|
+
|
67
|
+
def request(verb, path, opts={}, &block)
|
68
|
+
retries = 0
|
69
|
+
begin
|
70
|
+
raise AuthenticationError, "Authorize with Netlify before making requests" unless oauth_token
|
71
|
+
|
72
|
+
oauth_token.request(verb, ::File.join("/api", API_VERSION, path), opts, &block)
|
73
|
+
rescue OAuth2::Error => e
|
74
|
+
case e.response.status
|
75
|
+
when 401
|
76
|
+
raise AuthenticationError, message_for(e, "Authentication Error")
|
77
|
+
when 404
|
78
|
+
raise NotFoundError, message_for(e, "Not Found")
|
79
|
+
when 500
|
80
|
+
if retry_request?(verb, e.response.status, retries)
|
81
|
+
retries += 1
|
82
|
+
retry
|
83
|
+
else
|
84
|
+
raise InternalServerError, message_for(e, "Internal Server Error")
|
85
|
+
end
|
86
|
+
else
|
87
|
+
raise NetlifyError, message_for(e, "OAuth2 Error")
|
88
|
+
end
|
89
|
+
rescue Faraday::Error::ConnectionFailed, Faraday::Error::TimeoutError => e
|
90
|
+
if retry_request?(verb, e.response && e.response.status, retries)
|
91
|
+
retries += 1
|
92
|
+
retry
|
93
|
+
else
|
94
|
+
raise ConnectionError, message_for(e, "Connection Error")
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
def retry_request?(http_verb, status_code, retries)
|
101
|
+
return false unless [:get, :put, :delete, :head].include?(http_verb.to_s.downcase.to_sym)
|
102
|
+
return retries < 3 unless status_code && status_code == 422
|
103
|
+
end
|
104
|
+
|
105
|
+
def oauth_token
|
106
|
+
@oauth_token ||= access_token && OAuth2::AccessToken.new(oauth, access_token)
|
107
|
+
end
|
108
|
+
|
109
|
+
def message_for(error, default)
|
110
|
+
error.message.strip == "" ? default : error.message
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Netlify
|
2
|
+
class CollectionProxy
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
attr_accessor :client, :prefix
|
6
|
+
|
7
|
+
def self.path(value = nil)
|
8
|
+
return @path unless value
|
9
|
+
@path = value
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.model(value = nil)
|
13
|
+
@model ||= Netlify.const_get(to_s.split("::").last.sub(/s$/, ''))
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(client, prefix = nil)
|
17
|
+
self.client = client
|
18
|
+
self.prefix = prefix
|
19
|
+
end
|
20
|
+
|
21
|
+
def all(options = {})
|
22
|
+
response = client.request(:get, path, {:params => options})
|
23
|
+
response.parsed.map {|attributes| model.new(client, attributes.merge(:prefix => prefix)) } if response.parsed
|
24
|
+
end
|
25
|
+
|
26
|
+
def each(&block)
|
27
|
+
all.each(&block)
|
28
|
+
end
|
29
|
+
|
30
|
+
def get(id)
|
31
|
+
response = client.request(:get, ::File.join(path, id))
|
32
|
+
model.new(client, response.parsed.merge(:prefix => prefix)) if response.parsed
|
33
|
+
end
|
34
|
+
|
35
|
+
def create(attributes)
|
36
|
+
response = client.request(:post, path, :body => attributes)
|
37
|
+
model.new(client, response.parsed.merge(:prefix => prefix)) if response.parsed
|
38
|
+
end
|
39
|
+
|
40
|
+
def model
|
41
|
+
self.class.model
|
42
|
+
end
|
43
|
+
|
44
|
+
def path
|
45
|
+
[prefix, self.class.path].compact.join("/")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Netlify
|
2
|
+
class Deploy < Model
|
3
|
+
fields :id, :state, :premium, :claimed, :name, :custom_domain, :url,
|
4
|
+
:admin_url, :deploy_url, :screenshot_url, :created_at, :updated_at,
|
5
|
+
:user_id, :required
|
6
|
+
|
7
|
+
def upload_dir(dir)
|
8
|
+
return unless state == "uploading"
|
9
|
+
|
10
|
+
shas = Hash.new { [] }
|
11
|
+
glob = ::File.join(dir, "**", "*")
|
12
|
+
|
13
|
+
Dir.glob(glob) do |file|
|
14
|
+
next unless ::File.file?(file)
|
15
|
+
pathname = ::File.join("/", file[dir.length..-1])
|
16
|
+
next if pathname.match(/(^\/?__MACOSX\/|\/\.)/)
|
17
|
+
sha = Digest::SHA1.hexdigest(::File.read(file))
|
18
|
+
shas[sha] = shas[sha] + [pathname]
|
19
|
+
end
|
20
|
+
|
21
|
+
(required || []).each do |sha|
|
22
|
+
shas[sha].each do |pathname|
|
23
|
+
client.request(:put, ::File.join(path, "files", URI.encode(pathname)), :body => ::File.read(::File.join(dir, pathname)), :headers => {"Content-Type" => "application/octet-stream"})
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
refresh
|
28
|
+
end
|
29
|
+
|
30
|
+
def wait_for_ready(timeout = 900)
|
31
|
+
start = Time.now
|
32
|
+
while !(ready?)
|
33
|
+
sleep 5
|
34
|
+
refresh
|
35
|
+
puts "Got state: #{state}"
|
36
|
+
raise "Error processing site: #{error_message}" if error?
|
37
|
+
yield(self) if block_given?
|
38
|
+
raise "Timeout while waiting for ready" if Time.now - start > timeout
|
39
|
+
end
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
def ready?
|
44
|
+
state == "ready"
|
45
|
+
end
|
46
|
+
|
47
|
+
def error?
|
48
|
+
state == "error"
|
49
|
+
end
|
50
|
+
|
51
|
+
def publish
|
52
|
+
response = client.request(:post, ::File.join(path, "restore"))
|
53
|
+
process(response.parsed)
|
54
|
+
self
|
55
|
+
end
|
56
|
+
alias :restore :publish
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require "Netlify/deploy"
|
2
|
+
|
3
|
+
module Netlify
|
4
|
+
class Deploys < CollectionProxy
|
5
|
+
path "/deploys"
|
6
|
+
|
7
|
+
def create(attributes)
|
8
|
+
if attributes[:dir]
|
9
|
+
response = client.request(:post, path,
|
10
|
+
:body => JSON.generate({:files => inventory(attributes[:dir]), :draft => attributes[:draft] || false}),
|
11
|
+
:headers => {"Content-Type" => "application/json"}
|
12
|
+
)
|
13
|
+
Deploy.new(client, response.parsed).tap do |deploy|
|
14
|
+
deploy.upload_dir(attributes[:dir])
|
15
|
+
end
|
16
|
+
elsif attributes[:zip]
|
17
|
+
request_path = attributes[:draft] ? "#{path}?draft=true" : path
|
18
|
+
response = client.request(:post, request_path,
|
19
|
+
:body => ::File.read(attributes[:zip]),
|
20
|
+
:headers => {"Content-Type" => "application/zip"}
|
21
|
+
)
|
22
|
+
Deploy.new(client, response.parsed)
|
23
|
+
else
|
24
|
+
raise "Need dir or zip to create a deploy"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def draft(attributes)
|
29
|
+
create(attributes.merge(:draft => true))
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
def inventory(dir)
|
34
|
+
files = {}
|
35
|
+
Dir[::File.join(dir, "**", "*")].each do |file|
|
36
|
+
next unless ::File.file?(file)
|
37
|
+
path = ::File.join("/", file[dir.length..-1])
|
38
|
+
files[path] = Digest::SHA1.hexdigest(::File.read(file))
|
39
|
+
end
|
40
|
+
files
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/netlify/file.rb
ADDED
data/lib/netlify/form.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
module Netlify
|
2
|
+
class Model
|
3
|
+
attr_reader :client, :attributes, :prefix
|
4
|
+
|
5
|
+
def self.fields(*names)
|
6
|
+
return @fields if names.empty?
|
7
|
+
|
8
|
+
@fields ||= []
|
9
|
+
|
10
|
+
names.each do |name|
|
11
|
+
define_method name do
|
12
|
+
@attributes[name.to_sym]
|
13
|
+
end
|
14
|
+
|
15
|
+
define_method "#{name}=" do |value|
|
16
|
+
@attributes[name.to_sym] = value
|
17
|
+
end
|
18
|
+
|
19
|
+
@fields.push(name.to_sym)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.collection(value = nil)
|
24
|
+
@collection ||= Netlify.const_get(to_s.split("::").last + "s")
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(client, attributes)
|
28
|
+
@client = client
|
29
|
+
@attributes = {}
|
30
|
+
@prefix = attributes.delete(:prefix)
|
31
|
+
process(attributes)
|
32
|
+
end
|
33
|
+
|
34
|
+
def process(attributes)
|
35
|
+
self.class.fields.each do |field|
|
36
|
+
if attributes.has_key?(field) || attributes.has_key?(field.to_s)
|
37
|
+
@attributes[field] = attributes[field] || attributes[field.to_s]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
def update(attributes)
|
44
|
+
response = client.request(:put, path, :body => attributes)
|
45
|
+
process(response.parsed) if response.parsed
|
46
|
+
end
|
47
|
+
|
48
|
+
def destroy
|
49
|
+
client.request(:delete, path)
|
50
|
+
end
|
51
|
+
|
52
|
+
def refresh
|
53
|
+
response = client.request(:get, path)
|
54
|
+
process(response.parsed)
|
55
|
+
end
|
56
|
+
|
57
|
+
def collection
|
58
|
+
self.class.collection
|
59
|
+
end
|
60
|
+
|
61
|
+
def path
|
62
|
+
::File.join(*[prefix, collection.path, id.to_s].compact)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|