hetznercloud 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/Gemfile +6 -0
  4. data/LICENSE.md +21 -0
  5. data/README.md +123 -0
  6. data/config/inflections.rb +19 -0
  7. data/lib/core_ext/send_wrap.rb +15 -0
  8. data/lib/hcloud/action_collection.rb +37 -0
  9. data/lib/hcloud/client.rb +38 -0
  10. data/lib/hcloud/collection.rb +77 -0
  11. data/lib/hcloud/concerns/actionable.rb +29 -0
  12. data/lib/hcloud/concerns/concerns.rb +29 -0
  13. data/lib/hcloud/concerns/creatable.rb +46 -0
  14. data/lib/hcloud/concerns/deletable.rb +20 -0
  15. data/lib/hcloud/concerns/queryable.rb +41 -0
  16. data/lib/hcloud/concerns/updatable.rb +24 -0
  17. data/lib/hcloud/entities/amount.rb +8 -0
  18. data/lib/hcloud/entities/applied_to.rb +7 -0
  19. data/lib/hcloud/entities/applied_to_resource.rb +9 -0
  20. data/lib/hcloud/entities/apply_to.rb +15 -0
  21. data/lib/hcloud/entities/datacenter_server_type.rb +9 -0
  22. data/lib/hcloud/entities/dns_pointer.rb +8 -0
  23. data/lib/hcloud/entities/error.rb +8 -0
  24. data/lib/hcloud/entities/metrics.rb +12 -0
  25. data/lib/hcloud/entities/price.rb +9 -0
  26. data/lib/hcloud/entities/protection.rb +11 -0
  27. data/lib/hcloud/entities/rule.rb +15 -0
  28. data/lib/hcloud/entity.rb +23 -0
  29. data/lib/hcloud/errors.rb +36 -0
  30. data/lib/hcloud/http.rb +100 -0
  31. data/lib/hcloud/resource.rb +42 -0
  32. data/lib/hcloud/resource_type.rb +73 -0
  33. data/lib/hcloud/resources/action.rb +49 -0
  34. data/lib/hcloud/resources/datacenter.rb +43 -0
  35. data/lib/hcloud/resources/firewall.rb +80 -0
  36. data/lib/hcloud/resources/floating_ip.rb +144 -0
  37. data/lib/hcloud/resources/image.rb +140 -0
  38. data/lib/hcloud/resources/iso.rb +36 -0
  39. data/lib/hcloud/resources/location.rb +37 -0
  40. data/lib/hcloud/resources/placement_group.rb +75 -0
  41. data/lib/hcloud/resources/server.rb +179 -0
  42. data/lib/hcloud/resources/server_type.rb +42 -0
  43. data/lib/hcloud/resources/ssh_key.rb +77 -0
  44. data/lib/hcloud/resources/volume.rb +147 -0
  45. data/lib/hcloud/version.rb +17 -0
  46. data/lib/hcloud.rb +42 -0
  47. metadata +286 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 75623886cacdbf6d5513f45eb32c0d34ef0c8f0c1adee2bc7e69bf049b9c77ce
4
+ data.tar.gz: 8055fc431b7a33ef53c6f30ca9002e36f91e1c723197294bf1cc8c00d8035072
5
+ SHA512:
6
+ metadata.gz: ccc0bcd9ea354b43cd7b235b788f516a2665066580f44cd25e9d17d6e206680e3cc33786c14c1e47a5948c606588ef517e0a371a6de8537365e8697fea04a16d
7
+ data.tar.gz: cce03b3d75507f236152b04b731bd85cf88e15bef217805f6758e1fbf7dc12eb66e36b24717ee01680b16226f20d457b301436ca83d03aa183b6f7945f93dda0
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## HCloud v1.0.0
4
+
5
+ Initial release
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in hcloud.gemspec
6
+ gemspec
data/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Florian Dejonckheere
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,123 @@
1
+ # HCloud
2
+
3
+ ![Continuous Integration](https://github.com/floriandejonckheere/hcloud/workflows/Continuous%20Integration/badge.svg)
4
+ ![Release](https://img.shields.io/github/v/release/floriandejonckheere/hcloud?label=Latest%20release)
5
+
6
+ Unofficial Ruby integration with the [Hetzner Cloud API](https://docs.hetzner.cloud/).
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
14
+
15
+ gem "hetznercloud"
16
+ ```
17
+
18
+ And then execute:
19
+
20
+ $ bundle install
21
+
22
+ Or install it yourself as:
23
+
24
+ $ gem install hcloud
25
+
26
+ ## Usage
27
+
28
+ ```ruby
29
+ require "hcloud"
30
+
31
+ # Create a new client
32
+ client = HCloud::Client.new(access_token: "my_access_token")
33
+
34
+ # Set client as default connection
35
+ HCloud::Client.connection = client
36
+
37
+ # Create resource
38
+ ssh_key = HCloud::SSHKey.new(name: "My SSH key", public_key: "ssh-rsa ...")
39
+ ssh_key.create
40
+
41
+ # Alternate syntax:
42
+ ssh_key = HCloud::SSHKey.create(name: "My SSH key", public_key: "ssh-rsa ...")
43
+
44
+ # Find resource by ID
45
+ ssh_key = HCloud::SSHKey.find(1)
46
+
47
+ # Update resource
48
+ ssh_key.updatable_attributes # => [:name, :labels]
49
+ ssh_key.name = "New name"
50
+ ssh_key.update
51
+
52
+ # Delete resource
53
+ ssh_key.delete
54
+ ssh_key.deleted?
55
+ # => true
56
+
57
+ # For detailed usage of resources, refer to the class documentation
58
+
59
+ # When specifying associated resources, you can either use an instance of the resource, an integer as ID or a string as name.
60
+ # The following calls are equivalent:
61
+ server = HCloud::Server.new(name: "my_server", location: "fsn", ...)
62
+ server = HCloud::Server.new(name: "my_server", location: 1, ...)
63
+ server = HCloud::Server.new(name: "my_server", location: Location.new(name: "fsn"), ...)
64
+ ```
65
+
66
+ The gem aims to provide a simple, object-oriented interface to the Hetzner Cloud API.
67
+ It does not aim to be an authoritative source of information, and as such does little validation on your input data or behaviour.
68
+ It expects you to use it in a sane way.
69
+
70
+ ## Features
71
+
72
+ Not all Hetzner Cloud API endpoints have been implemented yet.
73
+
74
+ | Resource | State |
75
+ |-----------------------|-----------------------|
76
+ | Actions | Implemented |
77
+ | Certificates | Not implemented |
78
+ | Certificate Actions | Not implemented |
79
+ | Datacenters | Implemented |
80
+ | Firewalls | Implemented |
81
+ | Firewall Actions | Not implemented |
82
+ | Floating IPs | Implemented |
83
+ | Floating IP Actions | Implemented |
84
+ | Images | Implemented |
85
+ | Image Actions | Implemented |
86
+ | ISOs | Implemented |
87
+ | Load Balancers | Not implemented |
88
+ | Load Balancer Actions | Not implemented |
89
+ | Load Balancer Types | Not implemented |
90
+ | Locations | Implemented |
91
+ | Networks | Not implemented |
92
+ | Network Actions | Not implemented |
93
+ | Placement Groups | Implemented |
94
+ | Pricing | Not implemented |
95
+ | Servers | Partially implemented |
96
+ | Server Actions | Not implemented |
97
+ | Server Types | Implemented |
98
+ | SSH Keys | Implemented |
99
+ | Volumes | Implemented |
100
+ | Volume Actions | Implemented |
101
+
102
+ ## Testing
103
+
104
+ ```ssh
105
+ # Run test suite (without integration tests)
106
+ bundle exec rspec
107
+
108
+ # Run integration tests (WARNING: THIS WILL DESTROY **ALL** RESOURCES AFTER EACH RUN)
109
+ bundle exec rspec --tag integration
110
+ ```
111
+
112
+ ## Releasing
113
+
114
+ To release a new version, update the version number in `lib/hcloud/version.rb`, update the changelog, commit the files and create a git tag starting with `v`, and push it to the repository.
115
+ Github Actions will automatically run the test suite, build the `.gem` file and push it to [rubygems.org](https://rubygems.org).
116
+
117
+ ## Contributing
118
+
119
+ Bug reports and pull requests are welcome on GitHub at [https://github.com/floriandejonckheere/hcloud](https://github.com/floriandejonckheere/hcloud).
120
+
121
+ ## License
122
+
123
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ HCloud.loader.inflector.inflect(
4
+ "dns_pointer" => "DNSPointer",
5
+ "hcloud" => "HCloud",
6
+ "ssh_key" => "SSHKey",
7
+ "floating_ip" => "FloatingIP",
8
+ "http" => "HTTP",
9
+ "ip_not_available" => "IPNotAvailable",
10
+ "iso" => "ISO",
11
+ "iso_type" => "ISOType",
12
+ )
13
+
14
+ ActiveSupport::Inflector.inflections(:en) do |inflect|
15
+ inflect.acronym "DNS"
16
+ inflect.acronym "JSON"
17
+ inflect.acronym "IP"
18
+ inflect.acronym "ISO"
19
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CoreExt
4
+ module SendWrap
5
+ # Send a message to self, or all objects contained in self (for enumerables)
6
+
7
+ # FIXME: { env: "prod" }.send_wrap(:try, :to_h) returns { env: nil }
8
+
9
+ def send_wrap(method_name, ...)
10
+ is_a?(Array) ? map { |v| v.send(method_name, ...) } : send(method_name, ...)
11
+ end
12
+ end
13
+ end
14
+
15
+ Object.include CoreExt::SendWrap
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HCloud
4
+ class ActionCollection < Collection
5
+ attr_reader :resource
6
+
7
+ def initialize(resource)
8
+ super(&method(:all))
9
+
10
+ @resource = resource
11
+ end
12
+
13
+ def find(id)
14
+ Action.new resource
15
+ .client
16
+ .get("/#{resource.resource_name.pluralize}/#{resource.id}/actions/#{id}")
17
+ .fetch(:action)
18
+ end
19
+
20
+ private
21
+
22
+ def all(params)
23
+ response = resource
24
+ .client
25
+ .get("/#{resource.resource_name.pluralize}/#{resource.id}/actions", params)
26
+
27
+ data = response
28
+ .fetch(:actions)
29
+ .map { |attrs| Action.new attrs }
30
+
31
+ meta = response
32
+ .fetch(:meta)
33
+
34
+ [data, meta]
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "logger"
4
+
5
+ module HCloud
6
+ class NilConnection
7
+ def raise_error(...)
8
+ raise ArgumentError, "no default client configured, set HCloud::Client.connection to an instance of HCloud::Client"
9
+ end
10
+
11
+ alias get raise_error
12
+ alias put raise_error
13
+ alias post raise_error
14
+ alias delete raise_error
15
+ end
16
+
17
+ class Client
18
+ class_attribute :connection
19
+
20
+ self.connection = NilConnection.new
21
+
22
+ attr_reader :access_token, :endpoint, :logger
23
+
24
+ def initialize(access_token:, endpoint: "https://api.hetzner.cloud/v1", logger: Logger.new("/dev/null"))
25
+ @access_token = access_token
26
+ @endpoint = endpoint
27
+ @logger = logger
28
+ end
29
+
30
+ delegate :get, :put, :post, :delete, to: :http
31
+
32
+ private
33
+
34
+ def http
35
+ @http ||= HTTP.new(access_token, endpoint, logger)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HCloud
4
+ class Collection
5
+ include Enumerable
6
+
7
+ attr_accessor :page, :previous_page, :next_page, :last_page, :per_page, :total_entries, :proc
8
+
9
+ attr_reader :sort_by, :filter_by
10
+
11
+ def initialize(&block)
12
+ @proc = block
13
+
14
+ @page = 1
15
+ @per_page = 50
16
+
17
+ @sort_by = nil
18
+ @filter_by = {}
19
+ end
20
+
21
+ def sort(*sort_by)
22
+ @sort_by = sort_by
23
+
24
+ self
25
+ end
26
+
27
+ def where(**filter_by)
28
+ @filter_by = filter_by
29
+
30
+ self
31
+ end
32
+
33
+ def each(&block)
34
+ return to_enum(:each) unless block
35
+
36
+ loop do
37
+ # Fetch page
38
+ data, meta = proc.call(params)
39
+
40
+ # Yield data in page
41
+ data.each(&block)
42
+
43
+ # Set cursor and other page attributes
44
+ meta[:pagination].each { |k, v| send(:"#{k}=", v) }
45
+
46
+ break if page == last_page
47
+
48
+ # Increment page
49
+ @page += 1
50
+ end
51
+
52
+ self
53
+ end
54
+
55
+ def count
56
+ # Fetch total_entries if not present
57
+ @count ||= (total_entries || proc.call(params.merge(page: 1)).last.dig(:pagination, :total_entries))
58
+ end
59
+
60
+ def empty?
61
+ count.zero?
62
+ end
63
+
64
+ delegate :[], :last, to: :to_a
65
+
66
+ private
67
+
68
+ def params
69
+ {
70
+ page: page,
71
+ per_page: per_page,
72
+ sort: sort_by,
73
+ **filter_by,
74
+ }
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HCloud
4
+ module Actionable
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ class_attribute :action_names
9
+
10
+ self.action_names = []
11
+
12
+ def actions
13
+ ActionCollection.new(self)
14
+ end
15
+ end
16
+
17
+ class_methods do
18
+ def action(name)
19
+ action_names << name.to_s
20
+
21
+ define_method(name) do |**params|
22
+ Action.new client
23
+ .post("/#{resource_name.pluralize}/#{id}/actions/#{name}", params)
24
+ .fetch(:action)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HCloud
4
+ module Concerns
5
+ extend ActiveSupport::Concern
6
+
7
+ class_methods do
8
+ def actionable
9
+ include Actionable
10
+ end
11
+
12
+ def queryable
13
+ include Queryable
14
+ end
15
+
16
+ def creatable
17
+ include Creatable
18
+ end
19
+
20
+ def updatable
21
+ include Updatable
22
+ end
23
+
24
+ def deletable
25
+ include Deletable
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HCloud
4
+ module Creatable
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ attribute :created, :datetime
9
+
10
+ def create
11
+ assign_attributes client
12
+ .post("/#{resource_name.pluralize}", creatable_params)
13
+ .fetch(resource_name.to_sym)
14
+ end
15
+
16
+ def created?
17
+ created.present?
18
+ end
19
+
20
+ def creatable_attributes
21
+ []
22
+ end
23
+
24
+ # Convert creatable_attributes into a key-value list
25
+ # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
26
+ def creatable_params
27
+ # Split simple and nested attributes
28
+ nested_attributes, simple_attributes = creatable_attributes.partition { |a| a.respond_to? :each }
29
+
30
+ attributes
31
+ .slice(*simple_attributes.map(&:to_s))
32
+ .transform_values { |v| v&.send_wrap(:try, :to_h) || v&.send_wrap(:to_s) }
33
+ .merge(nested_attributes.reduce(&:merge)&.map { |k, v| [k.to_s, Array(v).filter_map { |w| send(k)&.send_wrap(w) }.first] }.to_h)
34
+ .compact
35
+ end
36
+ # rubocop:enable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
37
+ end
38
+
39
+ class_methods do
40
+ def create(**attributes)
41
+ new(attributes)
42
+ .tap(&:create)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HCloud
4
+ module Deletable
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ def delete
9
+ client
10
+ .delete("/#{resource_name.pluralize}/#{id}")
11
+
12
+ @deleted = true
13
+ end
14
+
15
+ def deleted?
16
+ @deleted.present?
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HCloud
4
+ module Queryable
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ def reload
9
+ assign_attributes client
10
+ .get("/#{resource_name.pluralize}/#{id}")
11
+ .fetch(resource_name.to_sym)
12
+
13
+ self
14
+ end
15
+ end
16
+
17
+ class_methods do
18
+ def find(id)
19
+ new client
20
+ .get("/#{resource_name.pluralize}/#{id}")
21
+ .fetch(resource_name.to_sym)
22
+ end
23
+
24
+ def all
25
+ Collection.new do |params|
26
+ response = client
27
+ .get("/#{resource_name.pluralize}", params)
28
+
29
+ data = response
30
+ .fetch(resource_name.pluralize.to_sym)
31
+ .map { |attrs| new attrs }
32
+
33
+ meta = response
34
+ .fetch(:meta)
35
+
36
+ [data, meta]
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HCloud
4
+ module Updatable
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ def update
9
+ assign_attributes client
10
+ .put("/#{resource_name.pluralize}/#{id}", updatable_params)
11
+ .fetch(resource_name.to_sym)
12
+ end
13
+
14
+ def updatable_attributes
15
+ []
16
+ end
17
+
18
+ # Convert updatable_attributes into a key-value list
19
+ def updatable_params
20
+ attributes.slice(*updatable_attributes.map(&:to_s))
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HCloud
4
+ class Amount < Entity
5
+ attribute :gross, :decimal
6
+ attribute :net, :decimal
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HCloud
4
+ class AppliedTo < ApplyTo
5
+ attribute :applied_to_resources, :applied_to_resource, array: true, default: -> { [] }
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HCloud
4
+ class AppliedToResource < Entity
5
+ attribute :type
6
+
7
+ attribute :server, :server
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HCloud
4
+ class ApplyTo < Entity
5
+ attribute :type
6
+
7
+ attribute :server, :server
8
+ attribute :label_selector
9
+
10
+ def to_h
11
+ # Omit `label_selector` if type is `server` and vice versa
12
+ super.except(type == "server" ? "label_selector" : "server")
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HCloud
4
+ class DatacenterServerType < Entity
5
+ attribute :available, array: true, default: -> { [] }
6
+ attribute :available_for_migration, array: true, default: -> { [] }
7
+ attribute :supported, array: true, default: -> { [] }
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HCloud
4
+ class DNSPointer < Entity
5
+ attribute :dns_ptr
6
+ attribute :ip
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HCloud
4
+ class Error < Entity
5
+ attribute :code
6
+ attribute :message
7
+ end
8
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HCloud
4
+ class Metrics < Entity
5
+ attribute :start, :datetime
6
+ attribute :end, :datetime
7
+
8
+ attribute :step, :integer
9
+
10
+ attribute :time_series
11
+ end
12
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HCloud
4
+ class Price < Entity
5
+ attribute :location
6
+ attribute :price_hourly, :amount
7
+ attribute :price_monthly, :amount
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HCloud
4
+ class Protection < Entity
5
+ attribute :delete, :boolean
6
+ attribute :rebuild, :boolean
7
+
8
+ alias delete? delete
9
+ alias rebuild? rebuild
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HCloud
4
+ class Rule < Entity
5
+ attribute :description
6
+
7
+ attribute :source_ips, array: true, default: -> { [] }
8
+ attribute :destination_ips, array: true, default: -> { [] }
9
+
10
+ attribute :port
11
+ attribute :protocol
12
+
13
+ attribute :direction
14
+ end
15
+ end