netlify 0.1.0
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 +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
|