hetznercloud 4.2.0 → 5.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 497195f54e986218ca478ec89a00fed2eeaf967e4f7cc1051b025de52290783b
4
- data.tar.gz: 6d0a810bfba0851a4f0957602080868259376b26f69130b743a419db644805c8
3
+ metadata.gz: 9175bc0a11981b096914d3a80869743db98d5691916f277c8eedb09f9c69b1e3
4
+ data.tar.gz: 7f1c35152b43e268bb3c39bbd57abb57f6010ad2ab5fd460c545064a5c11b593
5
5
  SHA512:
6
- metadata.gz: 52d6b19d64d69acd995e18f73a6fade7a85008512a13f66cd1f6ae6efc333556038f3e1768ce9ca4c86b9a742fe51ed22f9924a9475fc23256a3b487cc9b1949
7
- data.tar.gz: 6c32560938c2ec270218386ed537a82745d71f040088cd72bbfe6a6bd37c0caa8265f038ac73ec430cca7818c37d18f6286e5482e3521a284d734806708ec1fc
6
+ metadata.gz: 3bd92e7f000bdbbaad9065e3e8204d5bcb1d5a49b7d4b96fb56f9162101cd082bd810e4d0acbd51fbb42d3dee4e97b3c66a59f14c995badae3bac5c3354a49e9
7
+ data.tar.gz: '02896e1f2f35c3e3e5bb4179de52d3e711d6be7721b1b3726c269b1651a3bf3a41a79f0a327298eabdf65337647a62d06f63ae86d4c57b1ee5f10c1c89e0f464'
data/CHANGELOG.md CHANGED
@@ -10,6 +10,39 @@
10
10
 
11
11
  ### Fixed
12
12
 
13
+ ## HCloud v5.0.0 (2026-01-15)
14
+
15
+ ### Added
16
+
17
+ - Add `name` attribute to `HCloud::StorageBoxSubaccount`
18
+
19
+ ### Changed
20
+
21
+ - Rework RRSet resource handler
22
+
23
+ ### Removed
24
+
25
+ - Remove support for Ruby 3.0 and 3.1
26
+
27
+ ## HCloud v4.4.0 (2025-11-25)
28
+
29
+ ### Added
30
+
31
+ - Add `update_records` action to `HCloud::RRSet`
32
+
33
+ ## HCloud v4.3.0 (2025-11-17)
34
+
35
+ ### Added
36
+
37
+ - Implement Zones
38
+ - Implement Zone actions
39
+ - Implement RRSets
40
+ - Implement RRSet actions
41
+
42
+ ### Removed
43
+
44
+ - Removed global `ActiveSupport::Inflector` inflections
45
+
13
46
  ## HCloud v4.2.0 (2025-09-24)
14
47
 
15
48
  ### Added
data/Gemfile CHANGED
@@ -14,6 +14,7 @@ group :development, :test do
14
14
  gem "debug", require: false
15
15
  gem "dotenv", require: false
16
16
  gem "ffaker", require: false
17
+ gem "iconv", require: false
17
18
  gem "rake", require: false
18
19
  gem "rspec", require: false
19
20
  gem "rubocop", require: false
data/README.md CHANGED
@@ -92,6 +92,8 @@ The following table lists the Hetzner Cloud API endpoints that are currently imp
92
92
  | [Network Actions](lib/hcloud/resources/network.rb) | Implemented |
93
93
  | [Placement Groups](lib/hcloud/resources/placement_group.rb) | Implemented |
94
94
  | [Pricing](lib/hcloud/resources/pricing.rb) | Implemented |
95
+ | [RRSets](lib/hcloud/resources/rrset.rb) | Implemented |
96
+ | [RRSet Actions](lib/hcloud/resources/rrset.rb) | Implemented |
95
97
  | [Servers](lib/hcloud/resources/server.rb) | Partially implemented |
96
98
  | [Server Actions](lib/hcloud/resources/server.rb) | Not implemented |
97
99
  | [Server Types](lib/hcloud/resources/server_type.rb) | Implemented |
@@ -105,6 +107,8 @@ The following table lists the Hetzner Cloud API endpoints that are currently imp
105
107
  | [Volumes](lib/hcloud/resources/volume.rb) | Implemented |
106
108
  | [Volume Actions](lib/hcloud/resources/volume.rb) | Implemented |
107
109
  | [Metadata](lib/hcloud/resources/metadata.rb) | Implemented |
110
+ | [Zones](lib/hcloud/resources/zone.rb) | Implemented |
111
+ | [Zone Actions](lib/hcloud/resources/zone.rb) | Implemented |
108
112
 
109
113
  ### Pagination
110
114
 
@@ -17,19 +17,9 @@ HCloud.loader.inflector.inflect(
17
17
  "iso_type" => "ISOType",
18
18
  "primary_ip" => "PrimaryIP",
19
19
  "primary_ip_prices" => "PrimaryIPPrices",
20
+ "rrset" => "RRSet",
20
21
  "service_http" => "ServiceHTTP",
21
22
  "ssh_key" => "SSHKey",
22
23
  "target_ip" => "TargetIP",
23
24
  "yaml" => "YAML",
24
25
  )
25
-
26
- ActiveSupport::Inflector.inflections(:en) do |inflect|
27
- inflect.acronym "DNS"
28
- inflect.acronym "HTTP"
29
- inflect.acronym "IP"
30
- inflect.acronym "IPv4"
31
- inflect.acronym "IPv6"
32
- inflect.acronym "ISO"
33
- inflect.acronym "JSON"
34
- inflect.acronym "YAML"
35
- end
@@ -8,28 +8,30 @@ module HCloud
8
8
  included do # rubocop:disable Metrics/BlockLength
9
9
  attribute :created, :datetime
10
10
 
11
- def create
11
+ def create # rubocop:disable Metrics/AbcSize
12
12
  response = client
13
13
  .post(resource_path, creatable_params)
14
14
 
15
- # Some resources return an action instead of the resource itself (e.g. Storage Box API)
15
+ resource_key = resource_name.to_sym
16
+
17
+ if response.key?(resource_key)
18
+ # Set the attributes from the response
19
+ assign_attributes(response[resource_key].merge(response.slice(:root_password)))
20
+ end
21
+
22
+ # Some resources return an action instead of the resource itself
16
23
  if response.key?(:action)
17
24
  # Set the ID from the action response
18
- self.id = response
25
+ self.id ||= response
19
26
  .dig(:action, :resources)
20
27
  .find { |r| r[:type] == [resource_class&.resource_name, resource_name].compact.join("_") }
21
28
  .fetch(:id)
22
29
 
23
30
  # Return an Action instance
24
- Action.new response[:action]
25
- else
26
- # Set the attributes from the response
27
- assign_attributes response[resource_name.to_sym]
28
- .merge!(response.slice(:root_password))
29
-
30
- # Return the resource instance
31
- self
31
+ return Action.new(response[:action])
32
32
  end
33
+
34
+ self
33
35
  end
34
36
 
35
37
  def created?
@@ -5,6 +5,12 @@ module HCloud
5
5
  module Subresource
6
6
  extend ActiveSupport::Concern
7
7
 
8
+ delegate :resource_path, to: :class
9
+
10
+ def instance_path
11
+ resource_path(id: id)
12
+ end
13
+
8
14
  included do
9
15
  # For super resources
10
16
  class_attribute :subresource_names
@@ -15,7 +21,7 @@ module HCloud
15
21
  class_attribute :resource_class
16
22
  end
17
23
 
18
- class_methods do
24
+ class_methods do # rubocop:disable Metrics/BlockLength
19
25
  def inherited(subclass)
20
26
  super
21
27
 
@@ -35,27 +41,30 @@ module HCloud
35
41
 
36
42
  # For subresources
37
43
  def subresource_of(name, type = name)
44
+ raise ArgumentError, "Resource already a subresource of #{resource_class}" if resource_class.present?
45
+
38
46
  self.resource_class = ActiveModel::Type
39
47
  .lookup(type)
40
48
  .resource_class
41
49
 
42
- attribute name, :integer
50
+ attribute name, :integer # TODO: override that returns the resource class instance instead of the ID?
43
51
 
44
- # TODO: override that returns the resource class instance instead of the ID?
45
- end
52
+ # Nested resources (e.g. /storage_boxes/123/subaccounts)
53
+ define_method(:resource_path) do |id: nil|
54
+ super_instance = public_send(resource_class.resource_name)
55
+
56
+ return super() if super_instance.blank?
46
57
 
47
- def resource_path
48
- "/#{resource_name.pluralize}"
58
+ if super_instance.respond_to?(:instance_path)
59
+ [super_instance.instance_path, super(id: id)].join
60
+ else
61
+ [resource_class.resource_path(id: super_instance), super(id: id)].join
62
+ end
63
+ end
49
64
  end
50
- end
51
65
 
52
- def resource_path
53
- if resource_class && (id = send(resource_class.resource_name)).present?
54
- # Nested resources (e.g. /storage_boxes/123/subaccounts)
55
- "/#{resource_class.resource_name.pluralize}/#{id}/#{resource_name.pluralize}"
56
- else
57
- # Top-level resources (e.g. /servers)
58
- "/#{resource_name.pluralize}"
66
+ def resource_path(id: nil)
67
+ ["", resource_name.pluralize, id].compact.join("/")
59
68
  end
60
69
  end
61
70
  end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HCloud
4
+ class AuthoritativeNameservers < Entity
5
+ attribute :assigned, array: true, default: -> { [] }
6
+ attribute :delegated, array: true, default: -> { [] }
7
+ attribute :delegation_last_check, :datetime
8
+ attribute :delegation_status
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HCloud
4
+ class ChangeProtection < Entity
5
+ attribute :change, :boolean
6
+
7
+ alias change? change
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HCloud
4
+ class Nameserver < Entity
5
+ attribute :address
6
+ attribute :port, :integer
7
+
8
+ attribute :tsig_key
9
+ attribute :tsig_algorithm
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HCloud
4
+ class Record < Entity
5
+ attribute :value
6
+ attribute :comment
7
+ end
8
+ end
data/lib/hcloud/errors.rb CHANGED
@@ -29,6 +29,7 @@ module HCloud
29
29
  class FirewallAlreadyRemoved < Error; end
30
30
  class FirewallResourceNotFound < Error; end
31
31
  class Forbidden < Error; end
32
+ class IncorrectZoneMode < Error; end
32
33
  class IPNotAvailable < Error; end
33
34
  class IPNotOwned < Error; end
34
35
  class IncompatibleNetworkType < Error; end
data/lib/hcloud/http.rb CHANGED
@@ -107,16 +107,16 @@ module HCloud
107
107
  def transform_params(params)
108
108
  params
109
109
  .transform_values do |value|
110
- # Don't transform if value is single argument: { sort: :id }
111
- next value unless value.respond_to?(:each)
110
+ # Don't transform if value is single argument: { sort: :id }
111
+ next value unless value.respond_to?(:each)
112
112
 
113
- value.map do |element|
114
- # Don't transform if element is single argument: { sort: [:id] }
115
- next element unless element.respond_to?(:each)
113
+ value.map do |element|
114
+ # Don't transform if element is single argument: { sort: [:id] }
115
+ next element unless element.respond_to?(:each)
116
116
 
117
- # Join elements with : { sort: [id: :asc] }
118
- element.to_a.join(":")
119
- end
117
+ # Join elements with : { sort: [id: :asc] }
118
+ element.to_a.join(":")
119
+ end
120
120
  end
121
121
  .compact
122
122
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HCloud
4
+ class Inflector < Zeitwerk::Inflector
5
+ def underscore(classname)
6
+ classname
7
+ .to_s
8
+ .split("::")
9
+ .map { |word| overrides.key(word) || word.underscore }
10
+ .join("/")
11
+ end
12
+ end
13
+ end
@@ -15,6 +15,10 @@ module HCloud
15
15
  assign_attributes(attributes) if attributes
16
16
  end
17
17
 
18
+ def mutable?
19
+ true
20
+ end
21
+
18
22
  delegate :[], to: :attributes
19
23
 
20
24
  def inspect
@@ -31,8 +35,8 @@ module HCloud
31
35
  id && id == other.id
32
36
  end
33
37
 
34
- def self.attribute(name, *args, deprecated: false, **kwargs)
35
- super(name, *args, **kwargs)
38
+ def self.attribute(name, *, deprecated: false, **)
39
+ super(name, *, **)
36
40
 
37
41
  define_method(name) do |**params|
38
42
  warn "[DEPRECATION] Field \"#{name}\" on #{self.class.name} is deprecated." if deprecated
@@ -12,6 +12,10 @@ module HCloud
12
12
  @array = array
13
13
  end
14
14
 
15
+ def mutable?
16
+ true
17
+ end
18
+
15
19
  def cast(value)
16
20
  case value
17
21
  when nil, []
@@ -88,8 +92,10 @@ ActiveModel::Type.register(:amount, HCloud::ResourceType.Type("HCloud::Amount"))
88
92
  ActiveModel::Type.register(:applied_to, HCloud::ResourceType.Type("HCloud::AppliedTo"))
89
93
  ActiveModel::Type.register(:applied_to_resource, HCloud::ResourceType.Type("HCloud::AppliedToResource"))
90
94
  ActiveModel::Type.register(:apply_to, HCloud::ResourceType.Type("HCloud::ApplyTo"))
95
+ ActiveModel::Type.register(:authoritative_nameservers, HCloud::ResourceType.Type("HCloud::AuthoritativeNameservers"))
91
96
  ActiveModel::Type.register(:certificate, HCloud::ResourceType.Type("HCloud::Certificate"))
92
97
  ActiveModel::Type.register(:certificate_status, HCloud::ResourceType.Type("HCloud::CertificateStatus"))
98
+ ActiveModel::Type.register(:change_protection, HCloud::ResourceType.Type("HCloud::ChangeProtection"))
93
99
  ActiveModel::Type.register(:datacenter, HCloud::ResourceType.Type("HCloud::Datacenter"))
94
100
  ActiveModel::Type.register(:datacenter_server_type, HCloud::ResourceType.Type("HCloud::DatacenterServerType"))
95
101
  ActiveModel::Type.register(:deprecation, HCloud::ResourceType.Type("HCloud::Deprecation"))
@@ -115,6 +121,7 @@ ActiveModel::Type.register(:load_balancer_type, HCloud::ResourceType.Type("HClou
115
121
  ActiveModel::Type.register(:load_balancer_type_price, HCloud::ResourceType.Type("HCloud::LoadBalancerTypePrice"))
116
122
  ActiveModel::Type.register(:location, HCloud::ResourceType.Type("HCloud::Location"))
117
123
  ActiveModel::Type.register(:metadata, HCloud::ResourceType.Type("HCloud::Metadata"))
124
+ ActiveModel::Type.register(:nameserver, HCloud::ResourceType.Type("HCloud::Nameserver"))
118
125
  ActiveModel::Type.register(:network, HCloud::ResourceType.Type("HCloud::Network"))
119
126
  ActiveModel::Type.register(:placement_group, HCloud::ResourceType.Type("HCloud::PlacementGroup"))
120
127
  ActiveModel::Type.register(:price, HCloud::ResourceType.Type("HCloud::Price"))
@@ -126,8 +133,10 @@ ActiveModel::Type.register(:private_network, HCloud::ResourceType.Type("HCloud::
126
133
  ActiveModel::Type.register(:protection, HCloud::ResourceType.Type("HCloud::Protection"))
127
134
  ActiveModel::Type.register(:public_net, HCloud::ResourceType.Type("HCloud::PublicNet"))
128
135
  ActiveModel::Type.register(:public_net_firewall, HCloud::ResourceType.Type("HCloud::PublicNetFirewall"))
136
+ ActiveModel::Type.register(:record, HCloud::ResourceType.Type("HCloud::Record"))
129
137
  ActiveModel::Type.register(:resource, HCloud::ResourceType::GenericType)
130
138
  ActiveModel::Type.register(:route, HCloud::ResourceType.Type("HCloud::Route"))
139
+ ActiveModel::Type.register(:rrset, HCloud::ResourceType.Type("HCloud::RRSet"))
131
140
  ActiveModel::Type.register(:rule, HCloud::ResourceType.Type("HCloud::Rule"))
132
141
  ActiveModel::Type.register(:server, HCloud::ResourceType.Type("HCloud::Server"))
133
142
  ActiveModel::Type.register(:server_backup_price, HCloud::ResourceType.Type("HCloud::ServerBackupPrice"))
@@ -156,3 +165,4 @@ ActiveModel::Type.register(:traffic_price, HCloud::ResourceType.Type("HCloud::Tr
156
165
  ActiveModel::Type.register(:used_by, HCloud::ResourceType.Type("HCloud::UsedBy"))
157
166
  ActiveModel::Type.register(:volume, HCloud::ResourceType.Type("HCloud::Volume"))
158
167
  ActiveModel::Type.register(:volume_price, HCloud::ResourceType.Type("HCloud::VolumePrice"))
168
+ ActiveModel::Type.register(:zone, HCloud::ResourceType.Type("HCloud::Zone"))
@@ -48,28 +48,28 @@ module HCloud
48
48
  # = Actions
49
49
  # == List actions
50
50
  #
51
- # actions = HCloud::FloatingIP.find(1).actions
51
+ # actions = HCloud::Network.find(1).actions
52
52
  # # => [#<HCloud::Action id: 1, ...>, ...]
53
53
  #
54
54
  # == Sort actions
55
55
  #
56
- # HCloud::FloatingIP.find(1).actions.sort(finished: :desc)
56
+ # HCloud::Network.find(1).actions.sort(finished: :desc)
57
57
  # # => [#<HCloud::Action id: 1, ...>, ...]
58
58
  #
59
- # HCloud::FloatingIP.find(1).actions.sort(:command, finished: :asc)
59
+ # HCloud::Network.find(1).actions.sort(:command, finished: :asc)
60
60
  # # => [#<HCloud::Actions id: 1, ...>, ...]
61
61
  #
62
62
  # == Search actions
63
63
  #
64
- # HCloud::FloatingIP.find(1).actions.where(command: "assign_floating_ip")
64
+ # HCloud::Network.find(1).actions.where(command: "assign_floating_ip")
65
65
  # # => #<HCloud::Action id: 1, ...>
66
66
  #
67
- # HCloud::FloatingIP.find(1).actions.where(status: "success")
67
+ # HCloud::Network.find(1).actions.where(status: "success")
68
68
  # # => #<HCloud::Action id: 1, ...>
69
69
  #
70
70
  # == Find action by ID
71
71
  #
72
- # HCloud::FloatingIP.find(1).actions.find(1)
72
+ # HCloud::Network.find(1).actions.find(1)
73
73
  # # => #<HCloud::Action id: 1, ...>
74
74
  #
75
75
  # = Resource-specific actions
@@ -0,0 +1,177 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HCloud
4
+ ##
5
+ # Represents a DNS zone's RRSet
6
+ #
7
+ # == List all RRSets
8
+ #
9
+ # zone = HCloud::Zone.find(1)
10
+ # zone.rrsets
11
+ # # => [#<HCloud::RRSet id: 1, ...>, ...]
12
+ #
13
+ # == Sort RRSets
14
+ #
15
+ # zone = HCloud::Zone.find(1)
16
+ # zone.rrsets.sort(name: :desc)
17
+ # # => [#<HCloud::RRSet id: 1, ...>, ...]
18
+ #
19
+ # zone.rrsets.sort(:id, name: :asc)
20
+ # # => [#<HCloud::RRSet id: 1, ...>, ...]
21
+ #
22
+ # == Search RRSets
23
+ #
24
+ # zone = HCloud::Zone.find(1)
25
+ # zone.rrsets.where(name: "my_zone")
26
+ # # => #<HCloud::RRSet id: 1, ...>
27
+ #
28
+ # zone.rrsets.where(type: "AAAA")
29
+ # # => #<HCloud::RRSet id: 1, ...>
30
+ #
31
+ # zone.rrsets.where(label_selector: "environment=production")
32
+ # # => #<HCloud::RRSet id: 1, ...>
33
+ #
34
+ # == Find RRSet by ID
35
+ #
36
+ # zone = HCloud::Zone.find(1)
37
+ # zone.rrsets.find(1)
38
+ # # => #<HCloud::RRSet id: 1, ...>
39
+ #
40
+ # == Create RRSet
41
+ #
42
+ # zone = HCloud::Zone.find(1)
43
+ # rrset = zone.rrsets.new(name: "my_rrset", type: "A", ttl: 10800, records: [{ value: "198.51.100.1", comment: "My web server at Hetzner Cloud" }])
44
+ # rrset.create
45
+ # rrset.created?
46
+ # # => true
47
+ #
48
+ # == Update RRSet
49
+ #
50
+ # zone = HCloud::Zone.find(1)
51
+ # rrset = zone.rrsets.find(1)
52
+ # rrset.name = "another_rrset"
53
+ # rrset.update
54
+ #
55
+ # == Delete RRSet
56
+ #
57
+ # zone = HCloud::Zone.find(1)
58
+ # rrset = zone.rrsets.find(1)
59
+ # rrset.delete
60
+ # # => #<HCloud::Action id: 1, ...>
61
+ #
62
+ # rrset.deleted?
63
+ # # => true
64
+ #
65
+ # = Actions
66
+ # == List actions
67
+ #
68
+ # zone = HCloud::Zone.find(1)
69
+ # actions = zone.rrsets.find(1).actions
70
+ # # => [#<HCloud::Action id: 1, ...>, ...]
71
+ #
72
+ # == Sort actions
73
+ #
74
+ # zone = HCloud::Zone.find(1)
75
+ # zone.rrsets.find(1).actions.sort(finished: :desc)
76
+ # # => [#<HCloud::Action id: 1, ...>, ...]
77
+ #
78
+ # zone = HCloud::Zone.find(1)
79
+ # zone.rrsets.find(1).actions.sort(:command, finished: :asc)
80
+ # # => [#<HCloud::Action id: 1, ...>, ...]
81
+ #
82
+ # == Search actions
83
+ #
84
+ # zone = HCloud::Zone.find(1)
85
+ # zone.rrsets.find(1).actions.where(command: "change_protection")
86
+ # # => #<HCloud::Action id: 1, ...>
87
+ #
88
+ # zone = HCloud::Zone.find(1)
89
+ # zone.rrsets.find(1).actions.where(status: "success")
90
+ # # => #<HCloud::Action id: 1, ...>
91
+ #
92
+ # == Find action by ID
93
+ #
94
+ # zone = HCloud::Zone.find(1)
95
+ # zone.rrsets.find(1).actions.find(1)
96
+ # # => #<HCloud::Action id: 1, ...>
97
+ #
98
+ # = Resource-specific actions
99
+ # == Change protection
100
+ #
101
+ # zone = HCloud::Zone.find(1)
102
+ # zone.rrsets.find(1).change_protection(change: true)
103
+ # # => #<HCloud::Action id: 1, ...>
104
+ #
105
+ # == Change TTL
106
+ #
107
+ # zone = HCloud::Zone.find(1)
108
+ # zone.rrsets.find(1).change_ttl(ttl: 10800)
109
+ # # => #<HCloud::Action id: 1, ...>
110
+ #
111
+ # == Set records
112
+ #
113
+ # zone = HCloud::Zone.find(1)
114
+ # zone.rrsets.find(1).set_records(records: [{ value: "198.51.100.1", comment: "My web server at Hetzner Cloud" }])
115
+ # # => #<HCloud::Action id: 1, ...>
116
+ #
117
+ # == Add records
118
+ #
119
+ # zone = HCloud::Zone.find(1)
120
+ # zone.rrsets.find(1).add_records(ttl: 10800, records: [{ value: "198.51.100.1", comment: "My web server at Hetzner Cloud" }])
121
+ # # => #<HCloud::Action id: 1, ...>
122
+ #
123
+ # == Update records
124
+ #
125
+ # zone = HCloud::Zone.find(1)
126
+ # zone.rrsets.find(1).update_records(records: [{ value: "198.51.100.1", comment: "My web server at Hetzner Cloud" }])
127
+ # # => #<HCloud::Action id: 1, ...>
128
+ #
129
+ # == Remove records
130
+ #
131
+ # zone = HCloud::Zone.find(1)
132
+ # zone.rrsets.find(1).remove_records(records: [{ value: "198.51.100.1", comment: "My web server at Hetzner Cloud" }])
133
+ # # => #<HCloud::Action id: 1, ...>
134
+ #
135
+ class RRSet < Resource
136
+ subresource_of :zone
137
+
138
+ actionable
139
+ queryable
140
+ creatable
141
+ updatable
142
+ deletable
143
+ labelable
144
+
145
+ attribute :id
146
+ attribute :name
147
+
148
+ attribute :type
149
+
150
+ attribute :ttl, :integer
151
+
152
+ attribute :protection, :change_protection
153
+
154
+ attribute :records, :record, array: true, default: -> { [] }
155
+
156
+ attribute :zone, :zone
157
+
158
+ action :change_protection
159
+ action :change_ttl
160
+ action :set_records
161
+ action :add_records
162
+ action :update_records
163
+ action :remove_records
164
+
165
+ def creatable_attributes
166
+ [:name, :type, :ttl, :records, :labels]
167
+ end
168
+
169
+ def updatable_attributes
170
+ [:labels]
171
+ end
172
+
173
+ def self.resource_name
174
+ "rrset"
175
+ end
176
+ end
177
+ end
@@ -8,23 +8,23 @@ module HCloud
8
8
  #
9
9
  # storage_box = HCloud::StorageBox.find(1)
10
10
  # storage_box.snapshots
11
- # # => [#<HCloud::StorageBox::Snapshot id: 1, ...>, ...]
11
+ # # => [#<HCloud::StorageBoxSnapshot id: 1, ...>, ...]
12
12
  #
13
13
  # == Search snapshots
14
14
  #
15
15
  # storage_box = HCloud::StorageBox.find(1)
16
16
  # storage_box.snapshots.where(name: "monthly_backup")
17
- # # => #<HCloud::StorageBox::Snapshot id: 1, ...>
17
+ # # => #<HCloud::StorageBoxSnapshot id: 1, ...>
18
18
  #
19
19
  # storage_box = HCloud::StorageBox.find(1)
20
20
  # storage_box.snapshots.where(label_selector: "environment=production")
21
- # # => #<HCloud::StorageBox::Snapshot id: 1, ...>
21
+ # # => #<HCloud::StorageBoxSnapshot id: 1, ...>
22
22
  #
23
23
  # == Find snapshot by ID
24
24
  #
25
25
  # storage_box = HCloud::StorageBox.find(1)
26
26
  # storage_box.snapshots.find(1)
27
- # # => #<HCloud::StorageBox::Snapshot id: 1, ...>
27
+ # # => #<HCloud::StorageBoxSnapshot id: 1, ...>
28
28
  #
29
29
  # == Create snapshot
30
30
  #
@@ -46,7 +46,7 @@ module HCloud
46
46
  #
47
47
  # Note: this method returns a {HCloud::Action} instance rather than the created resource itself
48
48
  #
49
- # snapshot = HCloud::StorageBox::Snapshot.create(storage_box: 1, description: "my_snapshot")
49
+ # snapshot = HCloud::StorageBoxSnapshot.create(storage_box: 1, description: "my_snapshot")
50
50
  # # => #<HCloud::Action id: 1, ...>
51
51
  #
52
52
  # Note: this method returns a {HCloud::Action} instance rather than the created resource itself
@@ -8,19 +8,19 @@ module HCloud
8
8
  #
9
9
  # storage_box = HCloud::StorageBox.find(1)
10
10
  # storage_box.subaccounts
11
- # # => [#<HCloud::StorageBox::Subaccount id: 1, ...>, ...]
11
+ # # => [#<HCloud::StorageBoxSubaccount id: 1, ...>, ...]
12
12
  #
13
13
  # == Search subaccounts
14
14
  #
15
15
  # storage_box = HCloud::StorageBox.find(1)
16
16
  # storage_box.subaccounts.where(label_selector: "environment=production")
17
- # # => #<HCloud::StorageBox::Subaccount id: 1, ...>
17
+ # # => #<HCloud::StorageBoxSubaccount id: 1, ...>
18
18
  #
19
19
  # == Find subaccount by ID
20
20
  #
21
21
  # storage_box = HCloud::StorageBox.find(1)
22
22
  # storage_box.subaccounts.find(1)
23
- # # => #<HCloud::StorageBox::Subaccount id: 1, ...>
23
+ # # => #<HCloud::StorageBoxSubaccount id: 1, ...>
24
24
  #
25
25
  # == Create subaccount
26
26
  #
@@ -42,7 +42,7 @@ module HCloud
42
42
  #
43
43
  # Note: this method returns a {HCloud::Action} instance rather than the created resource itself
44
44
  #
45
- # subaccount = HCloud::StorageBox::Subaccount.create(storage_box: 1, password: "my_password", description: "my_subaccount", home_directory: "backup/", access_settings: { samba_enabled: false, ssh_enabled: true, webdav_enabled: false, readonly: false, reachable_externally: false })
45
+ # subaccount = HCloud::StorageBoxSubaccount.create(storage_box: 1, password: "my_password", description: "my_subaccount", home_directory: "backup/", access_settings: { samba_enabled: false, ssh_enabled: true, webdav_enabled: false, readonly: false, reachable_externally: false })
46
46
  # # => #<HCloud::Action id: 1, ...>
47
47
  #
48
48
  # Note: this method returns a {HCloud::Action} instance rather than the created resource itself
@@ -111,11 +111,11 @@ module HCloud
111
111
  action :update_access_settings
112
112
 
113
113
  def creatable_attributes
114
- [:password, :home_directory, :description, :labels, :access_settings]
114
+ [:password, :home_directory, :name, :description, :labels, :access_settings]
115
115
  end
116
116
 
117
117
  def updatable_attributes
118
- [:description, :labels]
118
+ [:name, :description, :labels]
119
119
  end
120
120
 
121
121
  def self.resource_name
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HCloud
4
+ ##
5
+ # Represents a DNS zone
6
+ #
7
+ # == List all zones
8
+ #
9
+ # HCloud::Zone.all
10
+ # # => [#<HCloud::Zone id: 1, ...>, ...]
11
+ #
12
+ # == Sort zones
13
+ #
14
+ # HCloud::Zone.sort(name: :desc)
15
+ # # => [#<HCloud::Zone id: 1, ...>, ...]
16
+ #
17
+ # HCloud::Zone.sort(:id, name: :asc)
18
+ # # => [#<HCloud::Zone id: 1, ...>, ...]
19
+ #
20
+ # == Search zones
21
+ #
22
+ # HCloud::Zone.where(name: "my_zone")
23
+ # # => #<HCloud::Zone id: 1, ...>
24
+ #
25
+ # HCloud::Zone.where(mode: "primary")
26
+ # # => #<HCloud::Zone id: 1, ...>
27
+ #
28
+ # HCloud::Zone.where(label_selector: "environment=production")
29
+ # # => #<HCloud::Zone id: 1, ...>
30
+ #
31
+ # == Find zone by ID
32
+ #
33
+ # HCloud::Zone.find(1)
34
+ # # => #<HCloud::Zone id: 1, ...>
35
+ #
36
+ # == Create zone
37
+ #
38
+ # zone = HCloud::Zone.new(name: "my_zone", mode: "primary", ttl: 10800, rrsets: [{ name: "www", type: "A", records: [{ value: "198.51.100.1", comment: "My web server at Hetzner Cloud" }] }])
39
+ # zone.create
40
+ # zone.created?
41
+ # # => true
42
+ #
43
+ # zone = HCloud::Zone.create(name: "my_zone", mode: "primary", ttl: 10800)
44
+ # # => #<HCloud::Zone id: 1, ...>
45
+ #
46
+ # == Update zone
47
+ #
48
+ # zone = HCloud::Zone.find(1)
49
+ # zone.labels = { environment: "production" }
50
+ # zone.update
51
+ #
52
+ # == Delete zone
53
+ #
54
+ # zone = HCloud::Zone.find(1)
55
+ # zone.delete
56
+ # zone.deleted?
57
+ # # => true
58
+ #
59
+ # == Export zone file
60
+ #
61
+ # zone = HCloud::Zone.find(1)
62
+ # zone.export
63
+ # # => "$ORIGIN\texample.com.\n$TTL\t3600\n\n@\tIN\tSOA\thydrogen.ns.hetzner.com. dns.hetzner.com. ..."
64
+ #
65
+ # = Actions
66
+ # == List actions
67
+ #
68
+ # actions = HCloud::Zone.find(1).actions
69
+ # # => [#<HCloud::Action id: 1, ...>, ...]
70
+ #
71
+ # == Sort actions
72
+ #
73
+ # HCloud::Zone.find(1).actions.sort(finished: :desc)
74
+ # # => [#<HCloud::Action id: 1, ...>, ...]
75
+ #
76
+ # HCloud::Zone.find(1).actions.sort(:command, finished: :asc)
77
+ # # => [#<HCloud::Actions id: 1, ...>, ...]
78
+ #
79
+ # == Search actions
80
+ #
81
+ # HCloud::Zone.find(1).actions.where(command: "import_zonefile")
82
+ # # => #<HCloud::Action id: 1, ...>
83
+ #
84
+ # HCloud::Zone.find(1).actions.where(status: "success")
85
+ # # => #<HCloud::Action id: 1, ...>
86
+ #
87
+ # == Find action by ID
88
+ #
89
+ # HCloud::Zone.find(1).actions.find(1)
90
+ # # => #<HCloud::Action id: 1, ...>
91
+ #
92
+ # = Resource-specific actions
93
+ # == Change a zone's primary nameservers
94
+ #
95
+ # HCloud::Zone.find(1).change_primary_nameservers(primary_nameservers: [{ address: "198.51.100.1", port: 53 }, { address: "203.0.113.1", port: 53 }])
96
+ # # => #<HCloud::Action id: 1, ...>
97
+ #
98
+ # == Change protection
99
+ #
100
+ # HCloud::Zone.find(1).change_protection(delete: true)
101
+ # # => #<HCloud::Action id: 1, ...>
102
+ #
103
+ # == Change default TTL
104
+ #
105
+ # HCloud::Zone.find(1).change_ttl(ttl: 10800)
106
+ # # => #<HCloud::Action id: 1, ...>
107
+ #
108
+ # == Import zone file
109
+ #
110
+ # HCloud::Zone.find(1).import_zonefile(zonefile: "$ORIGIN\texample.com.\n$TTL\t3600\n\n@\tIN\tSOA\thydrogen.ns.hetzner.com. dns.hetzner.com. ...")
111
+ # # => #<HCloud::Action id: 1, ...>
112
+ #
113
+ class Zone < Resource
114
+ actionable
115
+ queryable
116
+ creatable
117
+ updatable
118
+ deletable
119
+ labelable
120
+
121
+ subresource :rrset, :rrset
122
+
123
+ attribute :id, :integer
124
+ attribute :name
125
+
126
+ attribute :mode
127
+
128
+ attribute :primary_nameservers, :nameserver, array: true, default: -> { [] }
129
+ attribute :authoritative_nameservers, :authoritative_nameservers
130
+
131
+ attribute :record_count, :integer
132
+ attribute :ttl, :integer
133
+ attribute :status
134
+ attribute :registrar
135
+
136
+ attribute :protection, :protection
137
+
138
+ action :change_primary_nameservers
139
+ action :change_protection
140
+ action :change_ttl
141
+ action :import_zonefile
142
+
143
+ def export
144
+ raise Errors::MissingIDError unless id
145
+
146
+ client
147
+ .get("#{resource_path}/#{id}/zonefile")
148
+ .fetch(:zonefile)
149
+ end
150
+
151
+ def creatable_attributes
152
+ [:name, :mode, :ttl, :labels, :primary_nameservers, :rrsets]
153
+ end
154
+
155
+ def updatable_attributes
156
+ [:labels]
157
+ end
158
+ end
159
+ end
@@ -3,8 +3,8 @@
3
3
  module HCloud
4
4
  # @!visibility private
5
5
  module Version
6
- MAJOR = 4
7
- MINOR = 2
6
+ MAJOR = 5
7
+ MINOR = 0
8
8
  PATCH = 0
9
9
  PRE = nil
10
10
 
data/lib/hcloud.rb CHANGED
@@ -4,6 +4,8 @@ require "active_model"
4
4
  require "active_support/all"
5
5
  require "zeitwerk"
6
6
 
7
+ require_relative "hcloud/inflector"
8
+
7
9
  module HCloud
8
10
  class << self
9
11
  # Code loader instance
@@ -16,6 +18,9 @@ module HCloud
16
18
  def setup
17
19
  @loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
18
20
 
21
+ # Set up inflector
22
+ loader.inflector = Inflector.new
23
+
19
24
  # Register inflections
20
25
  require root.join("config/inflections.rb")
21
26
 
@@ -30,7 +30,7 @@ module HTTP
30
30
  self
31
31
  end
32
32
 
33
- def compress(&_block)
33
+ def compress(&)
34
34
  raise NotImplementedError
35
35
  end
36
36
 
@@ -17,10 +17,10 @@ module HTTP
17
17
  module Response
18
18
  # @!visibility private
19
19
  class BrotliInflater < HTTP::Response::Inflater
20
- def readpartial(*args)
20
+ def readpartial(*)
21
21
  chunks = []
22
22
 
23
- while (chunk = @connection.readpartial(*args))
23
+ while (chunk = @connection.readpartial(*))
24
24
  chunks << chunk
25
25
  end
26
26
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hetznercloud
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.2.0
4
+ version: 5.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Florian Dejonckheere
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-09-24 00:00:00.000000000 Z
11
+ date: 2026-01-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: openssl
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: zeitwerk
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -99,7 +113,9 @@ files:
99
113
  - lib/hcloud/entities/applied_to.rb
100
114
  - lib/hcloud/entities/applied_to_resource.rb
101
115
  - lib/hcloud/entities/apply_to.rb
116
+ - lib/hcloud/entities/authoritative_nameservers.rb
102
117
  - lib/hcloud/entities/certificate_status.rb
118
+ - lib/hcloud/entities/change_protection.rb
103
119
  - lib/hcloud/entities/datacenter_server_type.rb
104
120
  - lib/hcloud/entities/deprecation.rb
105
121
  - lib/hcloud/entities/dns_pointer.rb
@@ -117,6 +133,7 @@ files:
117
133
  - lib/hcloud/entities/load_balancer_public_net.rb
118
134
  - lib/hcloud/entities/load_balancer_type_price.rb
119
135
  - lib/hcloud/entities/metrics.rb
136
+ - lib/hcloud/entities/nameserver.rb
120
137
  - lib/hcloud/entities/price.rb
121
138
  - lib/hcloud/entities/primary_ip_prices.rb
122
139
  - lib/hcloud/entities/private_net.rb
@@ -124,6 +141,7 @@ files:
124
141
  - lib/hcloud/entities/protection.rb
125
142
  - lib/hcloud/entities/public_net.rb
126
143
  - lib/hcloud/entities/public_net_firewall.rb
144
+ - lib/hcloud/entities/record.rb
127
145
  - lib/hcloud/entities/route.rb
128
146
  - lib/hcloud/entities/rule.rb
129
147
  - lib/hcloud/entities/server_backup_price.rb
@@ -149,6 +167,7 @@ files:
149
167
  - lib/hcloud/entity.rb
150
168
  - lib/hcloud/errors.rb
151
169
  - lib/hcloud/http.rb
170
+ - lib/hcloud/inflector.rb
152
171
  - lib/hcloud/resource.rb
153
172
  - lib/hcloud/resource_type.rb
154
173
  - lib/hcloud/resources/action.rb
@@ -166,6 +185,7 @@ files:
166
185
  - lib/hcloud/resources/placement_group.rb
167
186
  - lib/hcloud/resources/pricing.rb
168
187
  - lib/hcloud/resources/primary_ip.rb
188
+ - lib/hcloud/resources/rrset.rb
169
189
  - lib/hcloud/resources/server.rb
170
190
  - lib/hcloud/resources/server_type.rb
171
191
  - lib/hcloud/resources/ssh_key.rb
@@ -174,6 +194,7 @@ files:
174
194
  - lib/hcloud/resources/storage_box_subaccount.rb
175
195
  - lib/hcloud/resources/storage_box_type.rb
176
196
  - lib/hcloud/resources/volume.rb
197
+ - lib/hcloud/resources/zone.rb
177
198
  - lib/hcloud/sub_collection.rb
178
199
  - lib/hcloud/version.rb
179
200
  - lib/http/features/block_io.rb
@@ -199,7 +220,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
199
220
  requirements:
200
221
  - - ">="
201
222
  - !ruby/object:Gem::Version
202
- version: '3.0'
223
+ version: '3.2'
203
224
  required_rubygems_version: !ruby/object:Gem::Requirement
204
225
  requirements:
205
226
  - - ">="