cloudflare 4.0.1 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 93312512447e91ad4a4e49d249d0474ce69304fbaed539a49cee2277afdc35f7
4
- data.tar.gz: 40c655960faf06672e9b0720cb9e24bfa2b47d09e87bc6e24f23b7def429f014
3
+ metadata.gz: 8f5bd083595954567afddacb3dccb6ad1dbe614d3d654d6ec5edfb7967d99e19
4
+ data.tar.gz: f1ac3f29decb6108455805f5a24d68cda0ab7a142cd781b0df991ed11014835f
5
5
  SHA512:
6
- metadata.gz: 212b1e7f5ec0c274676c6458a940eff14c9df4d2e43d990e1a03833bfa2ece184cb85e002a9eafe879ac6e8001e7750d23ebb07efb10bd8f1c02ae31686c5766
7
- data.tar.gz: c8e62b92827dc64b0833f821f07b526164a1da58cd408dfbe9b597d5c64a38d51ab805359655049a4dc98ce3e2e520f330ddf58f98039cfcb6e04dfdbeea5c2a
6
+ metadata.gz: 6113d2ffeb66def38c41b35ed739a24f2d8b568596e0fc5623e55c8b49dd17954235e37c0288ccef0aaf0a370c54c8bec5a4321cb5b2bfdc5f461a438184300d
7
+ data.tar.gz: bc5c727f94c5078d0a506d9cf6006b7d46582802d24a3089dbc3a8000be48e28a14d072f7a7a6125f835a14d567cd51f1424f2142dea44e992dafcfaff7d0d29
@@ -21,4 +21,5 @@ matrix:
21
21
 
22
22
  env:
23
23
  global:
24
- secure: c5yG7N1r9nYuw47Y90jGeoHNvkL59AAC55dtPAhKP+gjXWW8hKbntj3oj+lVkxEqzRpzEQgYZzUElrP+6mJnb20ldclZg03L56243tMtVEcpGOx/MFhnIBkx3kKu1H6ydKKMxieHxjsLQ3vnpcIZ3p0skTQjYbjdu607tjbyg7s=
24
+ - CLOUDFLARE_TEST_ZONE_MANAGEMENT=true
25
+ - secure: c5yG7N1r9nYuw47Y90jGeoHNvkL59AAC55dtPAhKP+gjXWW8hKbntj3oj+lVkxEqzRpzEQgYZzUElrP+6mJnb20ldclZg03L56243tMtVEcpGOx/MFhnIBkx3kKu1H6ydKKMxieHxjsLQ3vnpcIZ3p0skTQjYbjdu607tjbyg7s=
data/README.md CHANGED
@@ -36,7 +36,7 @@ require 'cloudflare'
36
36
  email = ENV['CLOUDFLARE_EMAIL']
37
37
  key = ENV['CLOUDFLARE_KEY']
38
38
 
39
- Cloudflare.connect(key: key, email: email) do
39
+ Cloudflare.connect(key: key, email: email) do |connection|
40
40
  # Get all available zones:
41
41
  zones = connection.zones
42
42
 
@@ -24,28 +24,24 @@
24
24
 
25
25
  require_relative 'representation'
26
26
  require_relative 'paginate'
27
+ require_relative 'kv/namespaces'
27
28
 
28
29
  module Cloudflare
29
30
  class Account < Representation
31
+ def id
32
+ value[:id]
33
+ end
34
+
35
+ def kv_namespaces
36
+ KV::Namespaces.new(@resource.with(path: 'storage/kv/namespaces'))
37
+ end
30
38
  end
31
-
39
+
32
40
  class Accounts < Representation
33
41
  include Paginate
34
-
35
- def represent(metadata, attributes)
36
- resource = @resource.with(path: attributes[:id])
37
-
38
- return Account.new(resource, metadata: metadata, value: attributes)
39
- end
40
-
41
- def create(name)
42
- response = self.post(name: name)
43
-
44
- return represent(response.headers, response.read)
45
- end
46
-
47
- def find_by_id(id)
48
- Zone.new(@resource.with(path: id))
42
+
43
+ def representation
44
+ Account
49
45
  end
50
46
  end
51
47
  end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './ssl_attribute/settings'
4
+
5
+ module Cloudflare
6
+ class CustomHostname < Representation
7
+ class SSLAttribute
8
+ def initialize(params)
9
+ @params = params
10
+ end
11
+
12
+ def active?
13
+ status == 'active'
14
+ end
15
+
16
+ def cname
17
+ @params[:cname]
18
+ end
19
+
20
+ def cname_target
21
+ @params[:cname_target]
22
+ end
23
+
24
+ def http_body
25
+ @params[:http_body]
26
+ end
27
+
28
+ def http_url
29
+ @params[:http_url]
30
+ end
31
+
32
+ def method
33
+ @params[:method]
34
+ end
35
+
36
+ def pending_validation?
37
+ status == 'pending_validation'
38
+ end
39
+
40
+ # Wraps the settings hash if it exists or initializes the settings hash and then wraps it
41
+ def settings
42
+ @settings ||= Settings.new(@params[:settings] ||= {})
43
+ end
44
+
45
+ def status
46
+ @params[:status]
47
+ end
48
+
49
+ def to_h
50
+ @params
51
+ end
52
+
53
+ def type
54
+ @params[:type]
55
+ end
56
+
57
+ def validation_errors
58
+ @params[:validation_errors]
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cloudflare
4
+ class CustomHostname < Representation
5
+ class SSLAttribute
6
+ class Settings
7
+ def initialize(settings)
8
+ @settings = settings
9
+ end
10
+
11
+ def ciphers
12
+ @settings[:ciphers]
13
+ end
14
+
15
+ def ciphers=(value)
16
+ @settings[:ciphers] = value
17
+ end
18
+
19
+ # This will return the raw value, it is needed because
20
+ # if a value is nil we can't assume that it means it is off
21
+ def http2
22
+ @settings[:http2]
23
+ end
24
+
25
+ # Always coerce into a boolean, if the key is not
26
+ # provided, this value may not be accurate
27
+ def http2?
28
+ http2 == 'on'
29
+ end
30
+
31
+ def http2=(value)
32
+ process_boolean(:http2, value)
33
+ end
34
+
35
+ def min_tls_version
36
+ @settings[:min_tls_version]
37
+ end
38
+
39
+ def min_tls_version=(value)
40
+ @settings[:min_tls_version] = value
41
+ end
42
+
43
+ # This will return the raw value, it is needed because
44
+ # if a value is nil we can't assume that it means it is off
45
+ def tls_1_3
46
+ @settings[:tls_1_3]
47
+ end
48
+
49
+ # Always coerce into a boolean, if the key is not
50
+ # provided, this value may not be accurate
51
+ def tls_1_3?
52
+ tls_1_3 == 'on'
53
+ end
54
+
55
+ def tls_1_3=(value)
56
+ process_boolean(:tls_1_3, value)
57
+ end
58
+
59
+ private
60
+
61
+ def process_boolean(key, value)
62
+ if value.nil?
63
+ @settings.delete(key)
64
+ else
65
+ @settings[key] = !value || value == 'off' ? 'off' : 'on'
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This implements the Custom Hostname API
4
+ # https://api.cloudflare.com/#custom-hostname-for-a-zone-properties
5
+
6
+ require_relative 'custom_hostname/ssl_attribute'
7
+ require_relative 'paginate'
8
+ require_relative 'representation'
9
+
10
+ module Cloudflare
11
+ class CustomHostname < Representation
12
+ # Only available if enabled for your zone
13
+ def custom_origin
14
+ value[:custom_origin_server]
15
+ end
16
+
17
+ # Only available if enabled for your zone
18
+ def custom_metadata
19
+ value[:custom_metadata]
20
+ end
21
+
22
+ def hostname
23
+ value[:hostname]
24
+ end
25
+
26
+ def id
27
+ value[:id]
28
+ end
29
+
30
+ def ssl
31
+ @ssl ||= SSLAttribute.new(value[:ssl])
32
+ end
33
+
34
+ # Check if the cert has been validated
35
+ # passing true will send a request to Cloudflare to try to validate the cert
36
+ def ssl_active?(force_update = false)
37
+ send_patch(ssl: { method: ssl.method, type: ssl.type }) if force_update && ssl.pending_validation?
38
+ ssl.active?
39
+ end
40
+
41
+ def update_settings(metadata: nil, origin: nil, ssl: nil)
42
+ attrs = {}
43
+ attrs[:custom_metadata] = metadata if metadata
44
+ attrs[:custom_origin_server] = origin if origin
45
+ attrs[:ssl] = ssl if ssl
46
+
47
+ send_patch(attrs)
48
+ end
49
+
50
+ alias :to_s :hostname
51
+
52
+ private
53
+
54
+ def send_patch(data)
55
+ response = patch(data)
56
+
57
+ @ssl = nil # Kill off our cached version of the ssl object so it will be regenerated from the response
58
+ @value = response.result
59
+ end
60
+ end
61
+
62
+ class CustomHostnames < Representation
63
+ include Paginate
64
+
65
+ def representation
66
+ CustomHostname
67
+ end
68
+
69
+ # initializes a custom hostname object and yields it for customization before saving
70
+ def create(hostname, metadata: nil, origin: nil, ssl: {}, &block)
71
+ attrs = { hostname: hostname, ssl: { method: 'http', type: 'dv' }.merge(ssl) }
72
+ attrs[:custom_metadata] = metadata if metadata
73
+ attrs[:custom_origin_server] = origin if origin
74
+
75
+ represent_message(self.post(attrs))
76
+ end
77
+
78
+ def find_by_hostname(hostname)
79
+ each(hostname: hostname).first
80
+ end
81
+ end
82
+ end
@@ -40,22 +40,22 @@ module Cloudflare
40
40
  name: @record[:name],
41
41
  content: content
42
42
  )
43
-
43
+
44
44
  @value = response.result
45
45
  end
46
-
46
+
47
47
  def type
48
48
  value[:type]
49
49
  end
50
-
50
+
51
51
  def name
52
52
  value[:name]
53
53
  end
54
-
54
+
55
55
  def content
56
56
  value[:content]
57
57
  end
58
-
58
+
59
59
  def to_s
60
60
  "#{@record[:name]} #{@record[:type]} #{@record[:content]}"
61
61
  end
@@ -63,29 +63,20 @@ module Cloudflare
63
63
 
64
64
  class Records < Representation
65
65
  include Paginate
66
-
66
+
67
67
  def representation
68
68
  Record
69
69
  end
70
-
70
+
71
71
  TTL_AUTO = 1
72
72
 
73
73
  def create(type, name, content, **options)
74
- message = self.post(type: type, name: name, content: content, **options)
75
-
76
- id = message.result[:id]
77
- resource = @resource.with(path: id)
78
-
79
- return representation.new(resource, metadata: message.headers, value: message.result)
74
+ represent_message(self.post(type: type, name: name, content: content, **options))
80
75
  end
81
-
76
+
82
77
  def find_by_name(name)
83
78
  each(name: name).first
84
79
  end
85
-
86
- def find_by_id(id)
87
- Record.new(@resource.with(path: id))
88
- end
89
80
  end
90
81
  end
91
82
  end
@@ -29,15 +29,15 @@ module Cloudflare
29
29
  def mode
30
30
  value[:mode]
31
31
  end
32
-
32
+
33
33
  def notes
34
34
  value[:notes]
35
35
  end
36
-
36
+
37
37
  def configuration
38
38
  value[:configuration]
39
39
  end
40
-
40
+
41
41
  def to_s
42
42
  "#{configuration[:value]} - #{mode} - #{notes}"
43
43
  end
@@ -45,14 +45,14 @@ module Cloudflare
45
45
 
46
46
  class Rules < Representation
47
47
  include Paginate
48
-
48
+
49
49
  def representation
50
50
  Rule
51
51
  end
52
-
52
+
53
53
  def set(mode, value, notes: nil, target: 'ip')
54
54
  notes ||= "cloudflare gem [#{mode}] #{Time.now.strftime('%m/%d/%y')}"
55
-
55
+
56
56
  message = self.post({
57
57
  mode: mode.to_s,
58
58
  notes: notes,
@@ -61,17 +61,10 @@ module Cloudflare
61
61
  value: value.to_s,
62
62
  }
63
63
  })
64
-
65
- id = message.result[:id]
66
- resource = @resource.with(path: id)
67
-
68
- return representation.new(resource, metadata: message.headers, value: message.result)
69
- end
70
-
71
- def find_by_id(id)
72
- Rule.new(@resource.with(path: id))
64
+
65
+ represent_message(message)
73
66
  end
74
-
67
+
75
68
  def each_by_value(value, &block)
76
69
  each(configuration_value: value, &block)
77
70
  end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This implements the Worker KV Store API
4
+ # https://api.cloudflare.com/#workers-kv-namespace-properties
5
+
6
+ require_relative '../paginate'
7
+ require_relative '../representation'
8
+
9
+ module Cloudflare
10
+ module KV
11
+ class Key < Representation
12
+ def name
13
+ value[:name]
14
+ end
15
+ end
16
+
17
+ class Keys < Representation
18
+ include Paginate
19
+
20
+ def representation
21
+ Key
22
+ end
23
+ end
24
+
25
+ class Namespace < Representation
26
+ def delete_value(name)
27
+ value_representation(name).delete.success?
28
+ end
29
+
30
+ def id
31
+ value[:id]
32
+ end
33
+
34
+ def keys
35
+ Keys.new(@resource.with(path: 'keys'))
36
+ end
37
+
38
+ def read_value(name)
39
+ value_representation(name).value
40
+ end
41
+
42
+ def rename(new_title)
43
+ put(title: new_title)
44
+ value[:title] = new_title
45
+ end
46
+
47
+ def title
48
+ value[:title]
49
+ end
50
+
51
+ def write_value(name, value)
52
+ value_representation(name).put(value).success?
53
+ end
54
+
55
+ private
56
+
57
+ def value_representation(name)
58
+ Representation.new(@resource.with(path: "values/#{name}"))
59
+ end
60
+ end
61
+
62
+ class Namespaces < Representation
63
+ include Paginate
64
+
65
+ def representation
66
+ Namespace
67
+ end
68
+
69
+ def create(title)
70
+ represent_message(post(title: title))
71
+ end
72
+
73
+ def find_by_title(title)
74
+ each.find {|namespace| namespace.title == title }
75
+ end
76
+ end
77
+ end
78
+ end
@@ -1,15 +1,15 @@
1
1
  # Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
- #
2
+ #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  # of this software and associated documentation files (the "Software"), to deal
5
5
  # in the Software without restriction, including without limitation the rights
6
6
  # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
7
  # copies of the Software, and to permit persons to whom the Software is
8
8
  # furnished to do so, subject to the following conditions:
9
- #
9
+ #
10
10
  # The above copyright notice and this permission notice shall be included in
11
11
  # all copies or substantial portions of the Software.
12
- #
12
+ #
13
13
  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
14
  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
15
  # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -21,34 +21,32 @@
21
21
  module Cloudflare
22
22
  module Paginate
23
23
  include Enumerable
24
-
25
- def empty?
26
- self.value.empty?
27
- end
28
-
29
- def representation
30
- Representation
31
- end
32
-
24
+
33
25
  def each(page: 1, per_page: 50, **parameters)
34
26
  return to_enum(:each, page: page, per_page: per_page, **parameters) unless block_given?
35
-
27
+
36
28
  while true
37
29
  zones = @resource.get(self.class, page: page, per_page: per_page, **parameters)
38
-
30
+
39
31
  break if zones.empty?
40
-
32
+
41
33
  Array(zones.value).each do |attributes|
42
- resource = @resource.with(path: attributes[:id])
43
-
44
- yield representation.new(resource, metadata: zones.metadata, value: attributes)
34
+ yield represent(zones.metadata, attributes)
45
35
  end
46
-
36
+
47
37
  page += 1
48
-
38
+
49
39
  # Was this the last page?
50
40
  break if zones.value.count < per_page
51
41
  end
52
42
  end
43
+
44
+ def empty?
45
+ self.value.empty?
46
+ end
47
+
48
+ def find_by_id(id)
49
+ representation.new(@resource.with(path: id))
50
+ end
53
51
  end
54
52
  end
@@ -29,62 +29,79 @@ module Cloudflare
29
29
  class RequestError < StandardError
30
30
  def initialize(resource, errors)
31
31
  super("#{resource}: #{errors.map{|attributes| attributes[:message]}.join(', ')}")
32
-
32
+
33
33
  @representation = representation
34
34
  end
35
-
35
+
36
36
  attr_reader :representation
37
37
  end
38
-
38
+
39
39
  class Message
40
40
  def initialize(response)
41
41
  @response = response
42
42
  @body = response.read
43
+
44
+ # Some endpoints return the value instead of a message object (like KV reads)
45
+ @body = { success: true, result: @body } unless @body.is_a?(Hash)
43
46
  end
44
-
47
+
45
48
  attr :response
46
49
  attr :body
47
-
50
+
48
51
  def headers
49
52
  @response.headers
50
53
  end
51
-
54
+
52
55
  def result
53
56
  @body[:result]
54
57
  end
55
-
58
+
56
59
  def read
57
60
  @body[:result]
58
61
  end
59
-
62
+
60
63
  def results
61
64
  Array(result)
62
65
  end
63
-
66
+
64
67
  def errors
65
68
  @body[:errors]
66
69
  end
67
-
70
+
68
71
  def messages
69
72
  @body[:messages]
70
73
  end
71
-
74
+
72
75
  def success?
73
76
  @body[:success]
74
77
  end
75
78
  end
76
-
79
+
77
80
  class Representation < Async::REST::Representation
78
81
  def process_response(*)
79
82
  message = Message.new(super)
80
-
83
+
81
84
  unless message.success?
82
85
  raise RequestError.new(@resource, message.errors)
83
86
  end
84
-
87
+
85
88
  return message
86
89
  end
87
-
90
+
91
+ def representation
92
+ Representation
93
+ end
94
+
95
+ def represent(metadata, attributes)
96
+ resource = @resource.with(path: attributes[:id])
97
+
98
+ representation.new(resource, metadata: metadata, value: attributes)
99
+ end
100
+
101
+ def represent_message(message)
102
+ represent(message.headers, message.result)
103
+ end
104
+
88
105
  def to_hash
89
106
  if value.is_a?(Hash)
90
107
  return value
@@ -22,5 +22,5 @@
22
22
  # THE SOFTWARE.
23
23
 
24
24
  module Cloudflare
25
- VERSION = '4.0.1'
25
+ VERSION = '4.1.0'
26
26
  end
@@ -25,12 +25,17 @@
25
25
  require_relative 'representation'
26
26
  require_relative 'paginate'
27
27
 
28
+ require_relative 'custom_hostnames'
28
29
  require_relative 'firewall'
29
30
  require_relative 'dns'
30
31
  require_relative 'logs'
31
32
 
32
33
  module Cloudflare
33
34
  class Zone < Representation
35
+ def custom_hostnames
36
+ CustomHostnames.new(@resource.with(path: 'custom_hostnames'))
37
+ end
38
+
34
39
  def dns_records
35
40
  DNS::Records.new(@resource.with(path: 'dns_records'))
36
41
  end
@@ -48,9 +53,9 @@ module Cloudflare
48
53
  }.freeze
49
54
 
50
55
  def purge_cache(parameters = DEFAULT_PURGE_CACHE_PARAMS)
51
- message = self.with(path: 'purge_cache').post(parameters)
56
+ Zone.new(@resource.with(path: 'purge_cache')).post(parameters)
52
57
 
53
- return message.success?
58
+ return self
54
59
  end
55
60
 
56
61
  def name
@@ -66,22 +71,13 @@ module Cloudflare
66
71
  def representation
67
72
  Zone
68
73
  end
69
-
74
+
70
75
  def create(name, account, jump_start = false)
71
- message = self.post(name: name, account: account.to_hash, jump_start: jump_start)
72
-
73
- id = message.result[:id]
74
- resource = @resource.with(path: id)
75
-
76
- return representation.new(resource, metadata: message.headers, value: message.result)
76
+ represent_message(self.post(name: name, account: account.to_hash, jump_start: jump_start))
77
77
  end
78
-
78
+
79
79
  def find_by_name(name)
80
80
  each(name: name).first
81
81
  end
82
-
83
- def find_by_id(id)
84
- Zone.new(@resource.with(path: id))
85
- end
86
82
  end
87
83
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Cloudflare::Accounts, order: :defined, timeout: 30 do
4
+ include_context Cloudflare::Account
5
+
6
+ before do
7
+ account.id # Force a fetch if it hasn't happened yet
8
+ end
9
+
10
+ it 'can list existing accounts' do
11
+ accounts = connection.accounts.to_a
12
+ expect(accounts.any? {|a| a.id == account.id }).to be true
13
+ end
14
+
15
+ it 'can get a specific account' do
16
+ expect(connection.accounts.find_by_id(account.id).id).to eq account.id
17
+ end
18
+
19
+ it 'can generate a representation for the KV namespace endpoint' do
20
+ ns = connection.accounts.find_by_id(account.id).kv_namespaces
21
+ expect(ns).to be_kind_of(Cloudflare::KV::Namespaces)
22
+ expect(ns.resource.reference.path).to end_with("/#{account.id}/storage/kv/namespaces")
23
+ end
24
+ end
@@ -0,0 +1,54 @@
1
+ RSpec.describe Cloudflare::CustomHostname::SSLAttribute::Settings do
2
+
3
+ subject { described_class.new({}) }
4
+
5
+ it 'has an accessor for ciphers' do
6
+ ciphers = double
7
+ expect(subject.ciphers).to be_nil
8
+ subject.ciphers = ciphers
9
+ expect(subject.ciphers).to be ciphers
10
+ end
11
+
12
+ it 'has a boolean accessor for http2' do
13
+ expect(subject.http2).to be_nil
14
+ expect(subject.http2?).to be false
15
+ subject.http2 = true
16
+ expect(subject.http2).to eq 'on'
17
+ expect(subject.http2?).to be true
18
+ subject.http2 = false
19
+ expect(subject.http2).to eq 'off'
20
+ expect(subject.http2?).to be false
21
+ subject.http2 = 'on'
22
+ expect(subject.http2).to eq 'on'
23
+ expect(subject.http2?).to be true
24
+ subject.http2 = 'off'
25
+ expect(subject.http2).to eq 'off'
26
+ expect(subject.http2?).to be false
27
+ end
28
+
29
+ it 'has an accessor for min_tls_version' do
30
+ tls_version = double
31
+ expect(subject.min_tls_version).to be_nil
32
+ subject.min_tls_version = tls_version
33
+ expect(subject.min_tls_version).to be tls_version
34
+ end
35
+
36
+ it 'has a boolean accessor for tls_1_3' do
37
+ expect(subject.tls_1_3).to be_nil
38
+ expect(subject.tls_1_3?).to be false
39
+ subject.tls_1_3 = true
40
+ expect(subject.tls_1_3).to eq 'on'
41
+ expect(subject.tls_1_3?).to be true
42
+ subject.tls_1_3 = false
43
+ expect(subject.tls_1_3).to eq 'off'
44
+ expect(subject.tls_1_3?).to be false
45
+ subject.tls_1_3 = 'on'
46
+ expect(subject.tls_1_3).to eq 'on'
47
+ expect(subject.tls_1_3?).to be true
48
+ subject.tls_1_3 = 'off'
49
+ expect(subject.tls_1_3).to eq 'off'
50
+ expect(subject.tls_1_3?).to be false
51
+ end
52
+
53
+
54
+ end
@@ -0,0 +1,73 @@
1
+ RSpec.describe Cloudflare::CustomHostname::SSLAttribute do
2
+
3
+ accessors = [:cname, :cname_target, :http_body, :http_url, :method, :status, :type, :validation_errors]
4
+
5
+ let(:original_hash) { {} }
6
+
7
+ subject { described_class.new(original_hash) }
8
+
9
+ accessors.each do |key|
10
+
11
+ it "has an accessor for the #{key} value" do
12
+ test_value = double
13
+ expect(subject.send(key)).to be_nil
14
+ original_hash[key] = test_value
15
+ expect(subject.send(key)).to be test_value
16
+ end
17
+
18
+ end
19
+
20
+ it '#active? returns true when the status is "active" and false otherwise' do
21
+ expect(subject.active?).to be false
22
+ original_hash[:status] = 'initializing'
23
+ expect(subject.active?).to be false
24
+ original_hash[:status] = 'pending_validation'
25
+ expect(subject.active?).to be false
26
+ original_hash[:status] = 'pending_deployment'
27
+ expect(subject.active?).to be false
28
+ original_hash[:status] = 'active'
29
+ expect(subject.active?).to be true
30
+ end
31
+
32
+ it '#pending_validation? returns true when the status is "pending_validation" and false otherwise' do
33
+ expect(subject.pending_validation?).to be false
34
+ original_hash[:status] = 'initializing'
35
+ expect(subject.pending_validation?).to be false
36
+ original_hash[:status] = 'active'
37
+ expect(subject.pending_validation?).to be false
38
+ original_hash[:status] = 'pending_deployment'
39
+ expect(subject.pending_validation?).to be false
40
+ original_hash[:status] = 'pending_validation'
41
+ expect(subject.pending_validation?).to be true
42
+ end
43
+
44
+ describe '#settings' do
45
+
46
+ it 'should return a Settings object' do
47
+ expect(subject.settings).to be_kind_of Cloudflare::CustomHostname::SSLAttribute::Settings
48
+ end
49
+
50
+ it 'initailizes the settings object with the value from the settings key' do
51
+ settings = { min_tls_version: double }
52
+ original_hash[:settings] = settings
53
+ expect(subject.settings.min_tls_version).to be settings[:min_tls_version]
54
+ end
55
+
56
+ it 'initializes the settings object with a new hash if the settings key does not exist' do
57
+ expected_value = double
58
+ expect(original_hash[:settings]).to be_nil
59
+ expect(subject.settings.min_tls_version).to be_nil
60
+ expect(original_hash[:settings]).not_to be_nil
61
+ original_hash[:settings][:min_tls_version] = expected_value
62
+ expect(subject.settings.min_tls_version).to be expected_value
63
+ end
64
+
65
+ it 'updates the stored hash with values set on the settings object' do
66
+ expected_value = double
67
+ expect(subject.settings.min_tls_version).to be_nil
68
+ subject.settings.min_tls_version = expected_value
69
+ expect(original_hash[:settings][:min_tls_version]).to be expected_value
70
+ end
71
+ end
72
+
73
+ end
@@ -0,0 +1,213 @@
1
+
2
+ RSpec.describe Cloudflare::CustomHostnames, order: :defined, timeout: 30 do
3
+ include_context Cloudflare::Zone
4
+
5
+ let(:domain) { "www#{ENV['TRAVIS_JOB_ID'] || rand(1..5)}.ourtest.com" }
6
+
7
+ let(:record) { @record = zone.custom_hostnames.create(domain) }
8
+
9
+ let(:custom_origin) do
10
+ id = rand(1...100)
11
+ id += (job_id * 100) if job_id.positive?
12
+ subdomain = "origin-#{id}"
13
+ @dns_record = zone.dns_records.create("A", subdomain, "1.2.3.4") # This needs to exist or the calls will fail
14
+ "#{subdomain}.#{zone.name}"
15
+ end
16
+
17
+ after do
18
+ if defined? @record
19
+ expect(@record.delete).to be_success
20
+ end
21
+
22
+ if defined? @dns_record
23
+ expect(@dns_record.delete).to be_success
24
+ end
25
+ end
26
+
27
+ it 'can create a custom hostname record' do
28
+ expect(record).to be_kind_of Cloudflare::CustomHostname
29
+ expect(record.custom_metadata).to be_nil
30
+ expect(record.hostname).to eq domain
31
+ expect(record.custom_origin).to be_nil
32
+ expect(record.ssl.method).to eq 'http'
33
+ expect(record.ssl.type).to eq 'dv'
34
+ end
35
+
36
+ it 'can create a custom hostname record with a custom origin' do
37
+ begin
38
+ @record = zone.custom_hostnames.create(domain, origin: custom_origin)
39
+
40
+ expect(@record).to be_kind_of Cloudflare::CustomHostname
41
+ expect(@record.custom_metadata).to be_nil
42
+ expect(@record.hostname).to eq domain
43
+ expect(@record.custom_origin).to eq custom_origin
44
+ expect(@record.ssl.method).to eq 'http'
45
+ expect(@record.ssl.type).to eq 'dv'
46
+ rescue Cloudflare::RequestError => e
47
+ if e.message.include?('custom origin server has not been granted')
48
+ skip(e.message) # This currently doesn't work but might start eventually: https://github.com/socketry/async-rspec/issues/7
49
+ else
50
+ raise
51
+ end
52
+ end
53
+ end
54
+
55
+ it 'can create a custom hostname record with different ssl options' do
56
+ @record = zone.custom_hostnames.create(domain, ssl: { method: 'cname' })
57
+
58
+ expect(@record).to be_kind_of Cloudflare::CustomHostname
59
+ expect(@record.custom_metadata).to be_nil
60
+ expect(@record.hostname).to eq domain
61
+ expect(@record.custom_origin).to be_nil
62
+ expect(@record.ssl.method).to eq 'cname'
63
+ expect(@record.ssl.type).to eq 'dv'
64
+ end
65
+
66
+ it 'can create a custom hostname record with additional metadata' do
67
+ metadata = { a: rand(1..10) }
68
+
69
+ begin
70
+ @record = zone.custom_hostnames.create(domain, metadata: metadata)
71
+
72
+ expect(@record).to be_kind_of Cloudflare::CustomHostname
73
+ expect(@record.custom_metadata).to eq metadata
74
+ expect(@record.hostname).to eq domain
75
+ expect(@record.custom_origin).to be_nil
76
+ expect(@record.ssl.method).to eq 'http'
77
+ expect(@record.ssl.type).to eq 'dv'
78
+ rescue Cloudflare::RequestError => e
79
+ if e.message.include?('No custom metadata access has been allocated for this zone')
80
+ skip(e.message) # This currently doesn't work but might start eventually: https://github.com/socketry/async-rspec/issues/7
81
+ else
82
+ raise
83
+ end
84
+ end
85
+ end
86
+
87
+ it 'can look up an existing custom hostname by the hostname or id' do
88
+ expect(zone.custom_hostnames.find_by_hostname(record.hostname).id).to eq record.id
89
+ expect(zone.custom_hostnames.find_by_id(record.id).id).to eq record.id
90
+ end
91
+
92
+ context 'with existing record' do
93
+
94
+ it 'returns the hostname when calling #to_s' do
95
+ expect(record.to_s).to eq domain
96
+ end
97
+
98
+ it 'can update metadata' do
99
+ metadata = { c: rand(1..10) }
100
+
101
+ expect(record.custom_metadata).to be_nil
102
+
103
+ begin
104
+ record.update_settings(metadata: metadata)
105
+
106
+ # Make sure the existing object is updated
107
+ expect(record.custom_metadata).to eq metadata
108
+
109
+ # Verify that the server has the changes
110
+ found_record = zone.custom_hostnames.find_by_id(record.id)
111
+
112
+ expect(found_record.custom_metadata).to eq metadata
113
+ expect(found_record.hostname).to eq domain
114
+ expect(found_record.custom_origin).to be_nil
115
+ rescue Cloudflare::RequestError => e
116
+ if e.message.include?('No custom metadata access has been allocated for this zone')
117
+ skip(e.message) # This currently doesn't work but might start eventually: https://github.com/socketry/async-rspec/issues/7
118
+ else
119
+ raise
120
+ end
121
+ end
122
+ end
123
+
124
+ it 'can update the custom origin' do
125
+ expect(record.custom_origin).to be_nil
126
+
127
+ begin
128
+ record.update_settings(origin: custom_origin)
129
+
130
+ # Make sure the existing object is updated
131
+ expect(record.custom_origin).to eq custom_origin
132
+
133
+ # Verify that the server has the changes
134
+ found_record = zone.custom_hostnames.find_by_id(record.id)
135
+
136
+ expect(found_record.custom_metadata).to be_nil
137
+ expect(found_record.hostname).to eq domain
138
+ expect(found_record.custom_origin).to eq custom_origin
139
+ rescue Cloudflare::RequestError => e
140
+ if e.message.include?('custom origin server has not been granted')
141
+ skip(e.message) # This currently doesn't work but might start eventually: https://github.com/socketry/async-rspec/issues/7
142
+ else
143
+ raise
144
+ end
145
+ end
146
+ end
147
+
148
+ it 'can update ssl information' do
149
+ expect(record.ssl.method).to eq 'http'
150
+
151
+ record.update_settings(ssl: { method: 'cname', type: 'dv' })
152
+
153
+ # Make sure the existing object is updated
154
+ expect(record.ssl.method).to eq 'cname'
155
+
156
+ # Verify that the server has the changes
157
+ found_record = zone.custom_hostnames.find_by_id(record.id)
158
+
159
+ expect(found_record.custom_metadata).to be_nil
160
+ expect(found_record.hostname).to eq domain
161
+ expect(found_record.custom_origin).to be_nil
162
+ expect(found_record.ssl.method).to eq 'cname'
163
+ end
164
+
165
+ context 'has an ssl section' do
166
+
167
+ it 'wraps it in an SSLAttributes object' do
168
+ expect(record.ssl).to be_kind_of Cloudflare::CustomHostname::SSLAttribute
169
+ end
170
+
171
+ it 'has some helpers for commonly used keys' do
172
+ # Make sure our values exist before we check to make sure that they are returned correctly
173
+ expect(record.value[:ssl].values_at(:method, :http_body, :http_url).compact).not_to be_empty
174
+ expect(record.ssl.method).to be record.value[:ssl][:method]
175
+ expect(record.ssl.http_body).to be record.value[:ssl][:http_body]
176
+ expect(record.ssl.http_url).to be record.value[:ssl][:http_url]
177
+ end
178
+
179
+ end
180
+
181
+ describe '#ssl_active?' do
182
+
183
+ it 'returns the result of calling ssl.active?' do
184
+ expected_value = double
185
+ expect(record.ssl).to receive(:active?).and_return(expected_value)
186
+ expect(record).not_to receive(:send_patch)
187
+ expect(record.ssl_active?).to be expected_value
188
+ end
189
+
190
+ it 'returns the result of calling ssl.active? without triggering an update if force_update is true and the ssl is not in the pending_validation state' do
191
+ expected_value = double
192
+ expect(record.ssl).to receive(:active?).and_return(expected_value)
193
+ expect(record.ssl.method).not_to be_nil
194
+ expect(record.ssl.type).not_to be_nil
195
+ expect(record.ssl.pending_validation?).to be false
196
+ expect(record).not_to receive(:send_patch).with(ssl: { method: record.ssl.method, type: record.ssl.type })
197
+ expect(record.ssl_active?(true)).to be expected_value
198
+ end
199
+
200
+ it 'returns the result of calling ssl.active? after triggering an update if force_update is true and the ssl is in the pending_validation state' do
201
+ expected_value = double
202
+ expect(record.ssl).to receive(:active?).and_return(expected_value)
203
+ expect(record.ssl.method).not_to be_nil
204
+ expect(record.ssl.type).not_to be_nil
205
+ record.value[:ssl][:status] = 'pending_validation'
206
+ expect(record).to receive(:send_patch).with(ssl: { method: record.ssl.method, type: record.ssl.type })
207
+ expect(record.ssl_active?(true)).to be expected_value
208
+ end
209
+
210
+ end
211
+
212
+ end
213
+ end
@@ -0,0 +1,71 @@
1
+
2
+ RSpec.describe Cloudflare::KV::Namespaces, kv_spec: true, order: :defined, timeout: 30 do
3
+ include_context Cloudflare::Account
4
+
5
+ let(:namespace) { @namespace = account.kv_namespaces.create(namespace_title) }
6
+ let(:namespace_title) { "Test NS ##{rand(1..100)}" }
7
+
8
+ after do
9
+ if defined? @namespace
10
+ expect(@namespace.delete).to be_success
11
+ end
12
+ end
13
+
14
+ it 'can create a namespace' do
15
+ expect(namespace).to be_kind_of Cloudflare::KV::Namespace
16
+ expect(namespace.id).not_to be_nil
17
+ expect(namespace.title).to eq namespace_title
18
+ end
19
+
20
+ it 'can find a namespace by title' do
21
+ namespace # Call this so that the namespace gets created
22
+ expect(account.kv_namespaces.find_by_title(namespace_title).id).to eq namespace.id
23
+ end
24
+
25
+ it 'can rename the namespace' do
26
+ new_title = "#{namespace_title}-#{rand(1..100)}"
27
+ namespace.rename(new_title)
28
+ expect(namespace.title).to eq new_title
29
+ expect(account.kv_namespaces.find_by_title(new_title).id).to eq namespace.id
30
+ expect(account.kv_namespaces.find_by_title(namespace_title)).to be_nil
31
+ end
32
+
33
+ it 'can store a key/value, read it back' do
34
+ key = "key-#{rand(1..100)}"
35
+ value = rand(100..999)
36
+ namespace.write_value(key, value)
37
+ expect(account.kv_namespaces.find_by_id(namespace.id).read_value(key)).to eq value.to_s
38
+ end
39
+
40
+ it 'can read a previously stored key' do
41
+ key = "key-#{rand(1..100)}"
42
+ value = rand(100..999)
43
+ expect(account.kv_namespaces.find_by_id(namespace.id).write_value(key, value)).to be true
44
+ expect(namespace.read_value(key)).to eq value.to_s
45
+ end
46
+
47
+ it 'can delete keys' do
48
+ key = "key-#{rand(1..100)}"
49
+ value = rand(100..999)
50
+ expect(namespace.write_value(key, value)).to be true
51
+ expect(namespace.read_value(key)).to eq value.to_s
52
+ expect(namespace.delete_value(key)).to be true
53
+ expect do
54
+ account.kv_namespaces.find_by_id(namespace.id).read_value(key)
55
+ end.to raise_error(Cloudflare::RequestError)
56
+ end
57
+
58
+ it 'can get the keys that exist in the namespace' do
59
+ counter = 0
60
+ keys = Array.new(rand(1..9)) { "key-#{counter += 1}" } # Keep this single digits so ordering works
61
+ keys.each_with_index do |key, i|
62
+ namespace.write_value(key, i)
63
+ end
64
+
65
+ saved_keys = account.kv_namespaces.find_by_id(namespace.id).keys.to_a
66
+ expect(saved_keys.length).to eq keys.length
67
+ saved_keys.each_with_index do |key, i|
68
+ expect(key.name).to eq keys[i]
69
+ end
70
+ end
71
+ end
@@ -1,23 +1,25 @@
1
1
 
2
2
  RSpec.describe Cloudflare::Zones, order: :defined, timeout: 30 do
3
3
  include_context Cloudflare::Zone
4
-
5
- it "can delete existing domain if exists" do
6
- if zone = zones.find_by_name(name)
7
- expect(zone.delete).to be_success
4
+
5
+ if ENV['CLOUDFLARE_TEST_ZONE_MANAGEMENT'] == 'true'
6
+ it "can delete existing domain if exists" do
7
+ if zone = zones.find_by_name(name)
8
+ expect(zone.delete).to be_success
9
+ end
10
+ end
11
+
12
+ it "can create a zone" do
13
+ zone = zones.create(name, account)
14
+ expect(zone.value).to include(:id)
8
15
  end
9
16
  end
10
-
11
- it "can create zone" do
12
- zone = zones.create(name, account)
13
- expect(zone.value).to include(:id)
14
- end
15
-
17
+
16
18
  it "can list zones" do
17
19
  matching_zones = zones.select{|zone| zone.name == name}
18
20
  expect(matching_zones).to_not be_empty
19
21
  end
20
-
22
+
21
23
  it "can get zone by name" do
22
24
  found_zone = zones.find_by_name(name)
23
25
  expect(found_zone.name).to be == name
@@ -1,3 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ AUTH_EMAIL = ENV['CLOUDFLARE_EMAIL']
4
+ AUTH_KEY = ENV['CLOUDFLARE_KEY']
5
+
6
+ if AUTH_EMAIL.nil? || AUTH_EMAIL.empty? || AUTH_KEY.nil? || AUTH_KEY.empty?
7
+ puts 'Please make sure you have defined CLOUDFLARE_EMAIL and CLOUDFLARE_KEY in your environment'
8
+ puts 'You can also specify CLOUDFLARE_ZONE_NAME to test with your own zone and'
9
+ puts 'CLOUDFLARE_ACCOUNT_ID to use a specific account'
10
+ exit(1)
11
+ end
12
+
13
+ ACCOUNT_ID = ENV['CLOUDFLARE_ACCOUNT_ID']
14
+ NAMES = ['testing', 'horse', 'cat', 'dog', 'fish', 'dolphin', 'lion', 'tiger'].freeze
15
+ JOB_ID = ENV.fetch('TRAVIS_JOB_ID', 0).to_i
16
+ ZONE_NAME = ENV['CLOUDFLARE_ZONE_NAME'] || "#{NAMES[JOB_ID % NAMES.size]}.com"
1
17
 
2
18
  require 'covered/rspec'
3
19
  require 'async/rspec'
@@ -5,18 +21,30 @@ require 'async/rspec'
5
21
  require 'cloudflare/rspec/connection'
6
22
  require 'cloudflare/zones'
7
23
 
8
- RSpec.shared_context Cloudflare::Zone do
24
+ RSpec.shared_context Cloudflare::Account do
9
25
  include_context Cloudflare::RSpec::Connection
10
-
11
- let(:job_id) {ENV.fetch('TRAVIS_JOB_ID', 0).to_i}
12
- let(:names) {['testing', 'horse', 'cat', 'dog', 'fish', 'dolphin', 'lion', 'tiger']}
13
- let(:name) {"#{names[job_id % names.size]}.com"}
14
-
15
- let(:account) {connection.accounts.first}
26
+
27
+ let(:account) do
28
+ if ACCOUNT_ID
29
+ connection.accounts.find_by_id(ACCOUNT_ID)
30
+ else
31
+ connection.accounts.first
32
+ end
33
+ end
34
+
35
+ end
36
+
37
+ RSpec.shared_context Cloudflare::Zone do
38
+ include_context Cloudflare::Account
39
+
40
+ let(:job_id) { JOB_ID }
41
+ let(:names) { NAMES.dup }
42
+ let(:name) { ZONE_NAME.dup }
43
+
16
44
  let(:zones) {connection.zones}
17
-
45
+
18
46
  let(:zone) {@zone = zones.find_by_name(name) || zones.create(name, account)}
19
-
47
+
20
48
  # after do
21
49
  # if defined? @zone
22
50
  # @zone.delete
@@ -31,4 +59,27 @@ RSpec.configure do |config|
31
59
  config.expect_with :rspec do |c|
32
60
  c.syntax = :expect
33
61
  end
62
+
63
+ disabled_specs = {}
64
+
65
+ # Check for features the current account has enabled
66
+ Cloudflare.connect(key: AUTH_KEY, email: AUTH_EMAIL) do |conn|
67
+ begin
68
+ account = if ACCOUNT_ID
69
+ conn.accounts.find_by_id(ACCOUNT_ID)
70
+ else
71
+ conn.accounts.first
72
+ end
73
+ account.kv_namespaces.to_a
74
+ rescue Cloudflare::RequestError => e
75
+ if e.message.include?('your account is not permitted')
76
+ puts 'Disabling KV specs due to no access'
77
+ disabled_specs[:kv_spec] = true
78
+ else
79
+ raise
80
+ end
81
+ end
82
+ end
83
+
84
+ config.filter_run_excluding disabled_specs unless disabled_specs.empty?
34
85
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cloudflare
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.1
4
+ version: 4.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marcin Prokop
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2019-02-09 00:00:00.000000000 Z
12
+ date: 2019-04-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: async-rest
@@ -113,8 +113,12 @@ files:
113
113
  - lib/cloudflare.rb
114
114
  - lib/cloudflare/accounts.rb
115
115
  - lib/cloudflare/connection.rb
116
+ - lib/cloudflare/custom_hostname/ssl_attribute.rb
117
+ - lib/cloudflare/custom_hostname/ssl_attribute/settings.rb
118
+ - lib/cloudflare/custom_hostnames.rb
116
119
  - lib/cloudflare/dns.rb
117
120
  - lib/cloudflare/firewall.rb
121
+ - lib/cloudflare/kv/namespaces.rb
118
122
  - lib/cloudflare/logs.rb
119
123
  - lib/cloudflare/paginate.rb
120
124
  - lib/cloudflare/representation.rb
@@ -122,8 +126,13 @@ files:
122
126
  - lib/cloudflare/user.rb
123
127
  - lib/cloudflare/version.rb
124
128
  - lib/cloudflare/zones.rb
129
+ - spec/cloudflare/accounts_spec.rb
130
+ - spec/cloudflare/custom_hostname/ssl_attribute/settings_spec.rb
131
+ - spec/cloudflare/custom_hostname/ssl_attribute_spec.rb
132
+ - spec/cloudflare/custom_hostnames_spec.rb
125
133
  - spec/cloudflare/dns_spec.rb
126
134
  - spec/cloudflare/firewall_spec.rb
135
+ - spec/cloudflare/kv/namespaces_spec.rb
127
136
  - spec/cloudflare/zone_spec.rb
128
137
  - spec/spec_helper.rb
129
138
  homepage: https://github.com/b4k3r/cloudflare
@@ -145,12 +154,17 @@ required_rubygems_version: !ruby/object:Gem::Requirement
145
154
  - !ruby/object:Gem::Version
146
155
  version: '0'
147
156
  requirements: []
148
- rubygems_version: 3.0.2
157
+ rubygems_version: 3.0.3
149
158
  signing_key:
150
159
  specification_version: 4
151
160
  summary: A Ruby wrapper for the Cloudflare API.
152
161
  test_files:
162
+ - spec/cloudflare/accounts_spec.rb
163
+ - spec/cloudflare/custom_hostname/ssl_attribute/settings_spec.rb
164
+ - spec/cloudflare/custom_hostname/ssl_attribute_spec.rb
165
+ - spec/cloudflare/custom_hostnames_spec.rb
153
166
  - spec/cloudflare/dns_spec.rb
154
167
  - spec/cloudflare/firewall_spec.rb
168
+ - spec/cloudflare/kv/namespaces_spec.rb
155
169
  - spec/cloudflare/zone_spec.rb
156
170
  - spec/spec_helper.rb