active_zone 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5883915c05075277429a22c1be2faa60d1b0e9b2a9dcff61f631589ed60c7d0c
4
+ data.tar.gz: 4d31ca5a2f065943356905f5f142d89ada3ac43a9e2468ec0f9c020df2a4fe9b
5
+ SHA512:
6
+ metadata.gz: d724771b2abf9bd36a2280ac1ae7d90df0cbc4c5305c08d74285ea527856f2adb82fef47f866dbd8d9fa209a59a8fa3dc8bde711fe403251c4d4894ef531e4f5
7
+ data.tar.gz: e1bfe415fc7a9d37ca0c834bbefb34fd461fc1793dd23e9fa101f9c6301ccaed205c79ee4344970136baffb2f01e4aa2943b02a230c42d071b93ef834ba3678b
data/LICENSE.md ADDED
@@ -0,0 +1,20 @@
1
+ Copyright Obl.ong
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # Active Zone
2
+ A Rails engine for managing database-backed DNS zones with various nameserver adapters.
3
+
4
+ ## Usage
5
+ How to use my plugin.
6
+
7
+ ## Installation
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem "active_zone"
12
+ ```
13
+
14
+ And then execute:
15
+ ```bash
16
+ $ bundle
17
+ ```
18
+
19
+ Or install it yourself as:
20
+ ```bash
21
+ $ gem install active_zone
22
+ ```
23
+
24
+ ## Contributing
25
+ Contribution directions go here.
26
+
27
+ ## License
28
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT), and aims to remain compatible with the [Ruby on Rails](https://github.com/rails/rails) licensing terms.
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ require "bundler/setup"
2
+
3
+ require "bundler/gem_tasks"
@@ -0,0 +1 @@
1
+ ActiveZone.provider = RactorDNS::ActiveZone.new(cpu_count: 24, zones: {})
@@ -0,0 +1,77 @@
1
+ module ActiveZone
2
+ # An Active Model class representing a DNS Zone at a provider.
3
+ # @abstract Subclass and override to create a functional Zone model
4
+ # @see Zone
5
+ # @see Zone::Builtins
6
+ class Provider::Zone
7
+ include ActiveModel::Model
8
+ include ActiveModel::Attributes
9
+ include ActiveModel::AttributeAssignment
10
+ include ActiveModel::Dirty
11
+ include ActiveModel::Conversion
12
+ include ActiveModel::Serialization
13
+ include ActiveModel::Validations
14
+ extend ActiveModel::Callbacks
15
+ include ActiveZone::Zone::Builtins
16
+
17
+ # Whether the current zone is persisted with a provider
18
+ # @api private
19
+ # @macro boolean
20
+ attr_reader :persisted
21
+
22
+ # @param provider [ActiveZone::Provider] Provider to store the zone with
23
+ # @param attributes [Hash] Attributes for the zone
24
+ # @option attributes [Array] :resources Array of resources mappable to {ActiveZone::Resource}
25
+ # @option attributes [String] :name Name of the zone
26
+ # @option attributes [TrueClass, FalseClass] :persisted Whether the zone is persisted at the provider
27
+ def initialize(provider = ActiveZone.provider, **attributes)
28
+ @provider = provider
29
+ (attributes[:resources] = attributes[:resources].map { |el| ActiveZone::Resource.new(**el) }) if attributes[:resources]
30
+ super(attributes)
31
+ clear_changes_information
32
+ end
33
+
34
+ # Delegate missing class methods to {all}
35
+ def self.method_missing(method_name, *, &)
36
+ all.send(method_name, *, &)
37
+ end
38
+
39
+ # (see method_missing)
40
+ # @macro boolean
41
+ def self.respond_to_missing?(method_name, include_private = false)
42
+ all.respond_to?(method_name, include_private)
43
+ end
44
+
45
+ attribute :name, :string, default: ""
46
+ attribute :persisted, default: false
47
+ attribute :resources, array: true, default: []
48
+ define_attribute_methods :name, :resources
49
+ # Provider the zone will be stored at
50
+ # @return [ActiveZone::Provider]
51
+ attr_reader :provider
52
+
53
+ define_model_callbacks :create, :save, :destroy
54
+
55
+ # Underlying method to save the current zone
56
+ # @api private
57
+ # @abstract Override in subclass with how to persist a Zone at your provider
58
+ # @macro boolean
59
+ def persist!(**options, &block)
60
+ raise NotImplementedError
61
+ end
62
+
63
+ # Underlying method to delete the current zone
64
+ # @api private
65
+ # @abstract Override in subclass with how to delete a Zone at your provider.
66
+ # @macro boolean
67
+ def delete!
68
+ raise NotImplementedError
69
+ end
70
+
71
+ # (see persisted)
72
+ # @see persisted
73
+ def persisted?
74
+ persisted
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,80 @@
1
+ module ActiveZone
2
+ # A queryable array-like class representing a collection of {Zone} at a provider
3
+ # @see https://edgeapi.rubyonrails.org/classes/ActiveRecord/Relation.html
4
+ # @abstract Subclass and override to create a queryable zone collection
5
+ class Provider::ZoneCollection
6
+ include Enumerable
7
+ # Provider the ZoneCollection is bound to
8
+ # @return [Provider]
9
+ attr_reader :provider
10
+ # Set of names in the ZoneCollection
11
+ # @return [Array]
12
+ # @return [nil] if all zones is desired
13
+ attr_accessor :set
14
+
15
+ # Initialize new ZoneCollection
16
+ # @param provider [ActiveZone::Provider] the provider
17
+ # @param set [Array] Array of names in the ZoneCollection, or nil for all zones
18
+ # @return ActiveZone::Provider::ZoneCollection
19
+ def initialize(provider, set = nil)
20
+ @provider = provider
21
+ @set = set
22
+ end
23
+
24
+ # Iterate over the zones at the provider
25
+ # @abstract Override and yield all zones in the set at the provider
26
+ # @yield [Zone] Zones within the {set} at the provider
27
+ # @raise {NotImplementedError}
28
+ def each(&block)
29
+ raise NotImplementedError
30
+ end
31
+
32
+ # Destroy all zones in this collection
33
+ # @return {nil}
34
+ # @abstract Override and destroy all zones in the set at the provider
35
+ # @raise {NotImplementedError}
36
+ def destroy_all
37
+ raise NotImplementedError
38
+ end
39
+
40
+ # Find a zone by name
41
+ # @return {Zone}
42
+ # @abstract Override and find the first zone by name in the set at the provider
43
+ # @raise {NotImplementedError}
44
+ def find(name)
45
+ raise NotImplementedError
46
+ end
47
+
48
+ # Find a zone by attribute in this collection
49
+ # @param attributes [Hash] attributes to filter by
50
+ # @return {Zone}
51
+ # @abstract Override and find the first zone that matches the provided attributes in the set at the provider
52
+ # @raise {NotImplementedError}
53
+ def find_by(**attributes)
54
+ raise NotImplementedError
55
+ end
56
+
57
+ # Find a zone by name inside this collection, if not found then create it
58
+ # @param attributes [Hash] the attributes of the zone to find by or create with
59
+ # @return {Zone}
60
+ def find_or_create_by(**attributes)
61
+ find_by(attributes) || provider.Zone.create(attributes)
62
+ end
63
+
64
+ # Update a zone by name in this collection, if not found then create it
65
+ # @param name [String] the name of the zone
66
+ # @return {Zone}
67
+ def upsert(name)
68
+ z = find_or_create_by(name:)
69
+ z.update(attributes)
70
+ end
71
+
72
+ # Get nth value in collection
73
+ # @abstract Override and get the nth value in the set at the provider
74
+ # @raise {NotImplementedError}
75
+ # @return {Zone}
76
+ def [](i)
77
+ raise NotImplementedError
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,19 @@
1
+ module ActiveZone
2
+ # The base class for Active Zone providers
3
+ # @abstract Subclass in your provider gem.
4
+ # @!method initialize(**options)
5
+ # @abstract Override in your provider subclass to configure & initialize your provider.
6
+ # @example Rails initializer
7
+ # ActiveZone.provider = YourProvider.new(username:, password:, base_url: "dns.example.com")
8
+ class Provider
9
+ # @return [Object<Zone>] Zone constant scoped under this Provider
10
+ def Zone
11
+ self.class.const_get(:Zone)
12
+ end
13
+
14
+ # @return [Object<ZoneCollection>] ZoneCollection constant scoped under this Provider
15
+ def ZoneCollection
16
+ self.class.const_get(:ZoneCollection)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,44 @@
1
+ # Simple wrapper for a complete resource
2
+ class ActiveZone::Resource
3
+ # @return [String]
4
+ attr_reader :name
5
+ # @return [Integer]
6
+ attr_reader :ttl
7
+ # @return [RRs::Resource]
8
+ attr_reader :rr
9
+
10
+ def initialize(zone: nil, name: "", ttl: 300, rr: nil)
11
+ @zone = zone
12
+ @name = name
13
+ @ttl = ttl
14
+ @rr = rr
15
+ @zone&.resources_will_change!
16
+ end
17
+
18
+ # Set name and mark changed on zone
19
+ # @param v [String]
20
+ def name=(v)
21
+ @name = v
22
+ @zone&.resources_will_change!
23
+ end
24
+
25
+ # Set TTL and mark changed on zone
26
+ # @param v [Integer]
27
+ def ttl=(v)
28
+ @ttl = v
29
+ @zone&.resources_will_change!
30
+ end
31
+
32
+ # Set RR and mark changed on zone
33
+ # @param v [RRs::Resource]
34
+ def rr=(v)
35
+ @rr = v
36
+ @zone&.resources_will_change!
37
+ end
38
+
39
+ # Convert to hash
40
+ # @return [Hash]
41
+ def to_h
42
+ {name:, ttl:, rr:}
43
+ end
44
+ end
@@ -0,0 +1,5 @@
1
+ module ActiveZone
2
+ # Version of the gem
3
+ # @return [String]
4
+ VERSION = "0.2.1"
5
+ end
@@ -0,0 +1,147 @@
1
+ module ActiveZone::Zone::Builtins
2
+ # Adds Active Record-like persistence methods to {ActiveZone::Provider::Zone}
3
+ # @see https://edgeapi.rubyonrails.org/classes/ActiveRecord/Persistence.html
4
+ module Persistence
5
+ extend ActiveSupport::Concern
6
+
7
+ class_methods do
8
+ # Creates and persists a {ActiveZone::Provider::Zone} without raising errors
9
+ # @return [ActiveZone::Provider::Zone]
10
+ # @return [FalseClass] if errored out
11
+ def create(attributes = nil, &block)
12
+ run_callbacks(:create) do
13
+ if attributes.is_a?(Array)
14
+ attributes.collect { |attr| create(attr, &block) }
15
+ else
16
+ object = new(attributes, &block)
17
+ object.save
18
+ object
19
+ end
20
+ end
21
+ end
22
+
23
+ # Creates and persists a {ActiveZone::Provider::Zone} and raises an error on failure
24
+ # @raise (see #save!)
25
+ # @return [ActiveZone::Provider::Zone]
26
+ def create!(attributes = nil, &block)
27
+ run_callbacks(:create) do
28
+ if attributes.is_a?(Array)
29
+ attributes.collect { |attr| create!(attr, &block) }
30
+ else
31
+ object = new(attributes, &block)
32
+ object.save!
33
+ object
34
+ end
35
+ end
36
+ end
37
+
38
+ # Bulk update attributes on a set of {ActiveZone::Provider::Zone}
39
+ # @return {ActiveZone::Provider::ZoneCollection}
40
+ # @return {ActiveZone::Provider::Zone}
41
+ # @return [FalseClass] if errored out
42
+ def update(name = :all, **attributes)
43
+ if name.is_a?(Array)
44
+ if name.any?(ActiveZone::Provider::Zone)
45
+ raise ArgumentError,
46
+ "You are passing an array of Zone instances to `update`. " \
47
+ "Please pass the names of the objects by calling `pluck(:name)` or `map(&:name)`."
48
+ end
49
+ name.map { |one_name| find(one_name) }.each_with_index { |object, namex|
50
+ object.update(attributes[namex])
51
+ }
52
+ elsif name == :all
53
+ all.each { |record| record.update(attributes) }
54
+ else
55
+ if name.is_a? ActiveZone::Provider::Zone
56
+ raise ArgumentError,
57
+ "You are passing an instance of Zone to `update`. " \
58
+ "Please pass the name of the object by calling `.name`."
59
+ end
60
+ object = find(name)
61
+ object.update(attributes)
62
+ object
63
+ end
64
+ end
65
+
66
+ # Bulk update attributes on a set of {ActiveZone::Provider::Zone} and raise errors on failure
67
+ # @return {ActiveZone::Provider::ZoneCollection}
68
+ # @return {ActiveZone::Provider::Zone}
69
+ # @raise (see Persistence#update!)
70
+ def update!(name = :all, **attributes)
71
+ if name.is_a?(Array)
72
+ if name.any?(ActiveZone::Provider::Zone)
73
+ raise ArgumentError,
74
+ "You are passing an array of Zone instances to `update!`. " \
75
+ "Please pass the ids of the objects by calling `pluck(:name)` or `map(&:name)`."
76
+ end
77
+ name.map { |one_name| find(one_name) }.each_with_index { |object, idx|
78
+ object.update!(attributes[idx])
79
+ }
80
+ elsif name == :all
81
+ all.each { |record| record.update!(attributes) }
82
+ else
83
+ if name.is_a? ActiveZone::Provider::Zone
84
+ raise ArgumentError,
85
+ "You are passing an instance of Zone to `update!`. " \
86
+ "Please pass the name of the object by calling `.name`."
87
+ end
88
+ object = find(name)
89
+ object.update!(attributes)
90
+ object
91
+ end
92
+ end
93
+ end
94
+
95
+ # Persists a {ActiveZone::Provider::Zone}
96
+ # @return [ActiveZone::Provider::Zone]
97
+ # @return [FalseClass] if errored out
98
+ def save(**options, &block)
99
+ run_callbacks(:save) do
100
+ persist!
101
+ end
102
+ self
103
+ rescue
104
+ false
105
+ end
106
+
107
+ # Persists a {ActiveZone::Provider::Zone} and raises an error on failure
108
+ # @raise {RuntimeError}
109
+ # @return [ActiveZone::Provider::Zone]
110
+ def save!(**, &)
111
+ save(**, &) || raise("Failed to save the zone")
112
+ end
113
+
114
+ # Destroys a {ActiveZone::Provider::Zone} at the provider and freezes the {Provider::Zone}
115
+ # @macro boolean
116
+ def destroy
117
+ run_callbacks(:destroy) do
118
+ delete! if persisted?
119
+ freeze
120
+ end
121
+ true
122
+ rescue
123
+ false
124
+ end
125
+
126
+ # Destroys a {ActiveZone::Provider::Zone} at the provider, freezes the {Provider::Zone} and raises errors on failure
127
+ # @return {TrueClass}
128
+ # @raise {RuntimeError}
129
+ def destroy!
130
+ destroy || raise("Failed to destroy the zone")
131
+ end
132
+
133
+ # Updates attributes of a {ActiveZone::Provider::Zone} and saves it
134
+ # @return (see #save)
135
+ def update(attributes)
136
+ assign_attributes(attributes)
137
+ save
138
+ end
139
+
140
+ # Updates attributes of a {ActiveZone::Provider::Zone}, saves it and raises errors on failure
141
+ # @return (see #save)
142
+ # @raise {RuntimeError}
143
+ def update!(attributes)
144
+ update || raise("Failed to update the zone")
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,36 @@
1
+ # A module of builtins for {ActiveZone::Provider::Zone}
2
+ module ActiveZone::Zone::Builtins
3
+ include Persistence
4
+ extend ActiveSupport::Concern
5
+
6
+ class_methods do
7
+ # @!scope class
8
+
9
+ @@provider = nil
10
+ @@semaphore = Mutex.new
11
+
12
+ # @return [ActiveZone::Provider]
13
+ # @api private
14
+ # Current provider in use for class methods, set dynamically by {with_provider}
15
+ attr_reader :provider
16
+ # @return [Mutex]
17
+ # @api private
18
+ # Used to lock current provider for class methods in {with_provider}
19
+ attr_reader :semaphore
20
+
21
+ # Locks the {ActiveZone::Provider} used in {ActiveZone::Provider::Zone} class methods and executes the block
22
+ def with_provider(provider, &block)
23
+ @@semaphore.synchronize {
24
+ @@provider = provider
25
+ val = block.call(self)
26
+ @@provider = nil
27
+ return val
28
+ }
29
+ end
30
+
31
+ # @return {Provider::ZoneCollection} for all zones at the current {Provider}
32
+ def all
33
+ @@provider&.ZoneCollection&.new(@@provider, nil)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,32 @@
1
+ # Proxy class for easily accessing {Provider::Zone} at the current {Provider}
2
+ # @see ActiveZone::Provider::Zone
3
+ class ActiveZone::Zone
4
+ # @param attributes [Hash] attributes for the new {Provider::Zone}
5
+ def initialize(**attributes)
6
+ @zone = ActiveZone.provider.Zone.new(**attributes)
7
+ end
8
+
9
+ # Delegate to {Provider::Zone}
10
+ def method_missing(method_name, *, &)
11
+ @zone.send(method_name, *, &)
12
+ end
13
+
14
+ # Delegate to {Provider::Zone}
15
+ # @macro boolean
16
+ def respond_to_missing?(method_name, include_private = false)
17
+ @zone.respond_to?(method_name, include_private)
18
+ end
19
+
20
+ # Delegate class methods to current [Provider::Zone]
21
+ def self.method_missing(method_name, *arguments, &block)
22
+ ActiveZone.provider.Zone.with_provider(ActiveZone.provider) do |z|
23
+ z.send(method_name, *arguments, &block)
24
+ end
25
+ end
26
+
27
+ # Delegate class methods to current [Provider::Zone]
28
+ # @macro boolean
29
+ def self.respond_to_missing?(method_name, include_private = false)
30
+ ActiveZone.provider.Zone.respond_to?(method_name, include_private)
31
+ end
32
+ end
@@ -0,0 +1,18 @@
1
+ require "active_support/all"
2
+ require "rrs"
3
+ require "ractor_dns"
4
+ require "zeitwerk"
5
+
6
+ loader = Zeitwerk::Loader.for_gem
7
+ loader.setup
8
+
9
+ # Base Active Zone module
10
+ module ActiveZone
11
+ # Current default provider
12
+ # @return [Provider]
13
+ mattr_accessor :provider
14
+ end
15
+
16
+ # @!macro [new] boolean
17
+ # @return [TrueClass] if true
18
+ # @return [FalseClass] if false
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: active_zone
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.1
5
+ platform: ruby
6
+ authors:
7
+ - Obl.ong
8
+ - Reese Armstrong
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2024-08-24 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activesupport
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: 7.2.0
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: 7.2.0
28
+ - !ruby/object:Gem::Dependency
29
+ name: zeitwerk
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '2.6'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '2.6'
42
+ - !ruby/object:Gem::Dependency
43
+ name: rrs
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: 0.2.0
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: 0.2.0
56
+ description: A Rails engine for managing database-backed DNS zones with various nameserver
57
+ adapters.
58
+ email:
59
+ - team@obl.ong
60
+ executables: []
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - LICENSE.md
65
+ - README.md
66
+ - Rakefile
67
+ - config/initializers/provider.rb
68
+ - lib/active_zone.rb
69
+ - lib/active_zone/provider.rb
70
+ - lib/active_zone/provider/zone.rb
71
+ - lib/active_zone/provider/zone_collection.rb
72
+ - lib/active_zone/resource.rb
73
+ - lib/active_zone/version.rb
74
+ - lib/active_zone/zone.rb
75
+ - lib/active_zone/zone/builtins.rb
76
+ - lib/active_zone/zone/builtins/persistence.rb
77
+ homepage: https://github.com/obl-ong/active_zone
78
+ licenses:
79
+ - MIT
80
+ metadata:
81
+ allowed_push_host: https://rubygems.org
82
+ homepage_uri: https://github.com/obl-ong/active_zone
83
+ source_code_uri: https://github.com/obl-ong/active_zone
84
+ changelog_uri: https://github.com/obl-ong/active_zone/releases
85
+ post_install_message:
86
+ rdoc_options: []
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ requirements: []
100
+ rubygems_version: 3.5.17
101
+ signing_key:
102
+ specification_version: 4
103
+ summary: Manage database-backed DNS zones with Rails
104
+ test_files: []