path53 0.4.3

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 99b785b7695fe63239d1230482f5d81cef35094f
4
+ data.tar.gz: 7ae5a98ea8d3b1aad7f80f26f561a2bcb6279fc1
5
+ SHA512:
6
+ metadata.gz: f8ead285292146d38902ac18c8423f17d5f8be25d2d23cad2ae2898e4a80cff33d7a67cb0c11f7b706eb6392516d91e89274cd743eb49236fc5ed3488032aa12
7
+ data.tar.gz: a216d96699d5d1c3879682d45b683233ca48fcca8e986f687f02c5053fdb576d68bc92c33c59de188e3aa290a7cc3ce91b80d4952e67ee3fc01e1ff39f37abb2
data/.gitignore ADDED
@@ -0,0 +1,54 @@
1
+
2
+ # Created by https://www.gitignore.io/api/ruby
3
+
4
+ ### Ruby ###
5
+ *.gem
6
+ *.rbc
7
+ /.config
8
+ /coverage/
9
+ /InstalledFiles
10
+ /pkg/
11
+ /spec/reports/
12
+ /spec/examples.txt
13
+ /test/tmp/
14
+ /test/version_tmp/
15
+ /tmp/
16
+
17
+ # Used by dotenv library to load environment variables.
18
+ # .env
19
+
20
+ ## Specific to RubyMotion:
21
+ .dat*
22
+ .repl_history
23
+ build/
24
+ *.bridgesupport
25
+ build-iPhoneOS/
26
+ build-iPhoneSimulator/
27
+
28
+ ## Specific to RubyMotion (use of CocoaPods):
29
+ #
30
+ # We recommend against adding the Pods directory to your .gitignore. However
31
+ # you should judge for yourself, the pros and cons are mentioned at:
32
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
33
+ #
34
+ # vendor/Pods/
35
+
36
+ ## Documentation cache and generated files:
37
+ /.yardoc/
38
+ /_yardoc/
39
+ /doc/
40
+ /rdoc/
41
+
42
+ ## Environment normalization:
43
+ /.bundle/
44
+ /vendor/bundle
45
+ /lib/bundler/man/
46
+
47
+ # for a library or gem, you might want to ignore these files since the code is
48
+ # intended to run in multiple environments; otherwise, check them in:
49
+ # Gemfile.lock
50
+ # .ruby-version
51
+ # .ruby-gemset
52
+
53
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
54
+ .rvmrc
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+
2
+ The MIT License (MIT)
3
+ Copyright © 2016 Chris Olstrom <chris@olstrom.com>
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.org ADDED
@@ -0,0 +1,137 @@
1
+ #+TITLE: Path53
2
+ #+LATEX: \pagebreak
3
+
4
+ * Overview
5
+
6
+ =path53= provides a cleaner, simpler interface to Route53 change requests.
7
+
8
+ * Why does this exist?
9
+
10
+ Because making simple changes is unreasonably complicated. Route53's errors are
11
+ generic, the request structure is a nested mess of complexity, and the
12
+ validation rules are often surprising. =path53= aims to tame this complexity.
13
+
14
+ * Installation
15
+
16
+ #+BEGIN_SRC shell
17
+ gem install path53
18
+ #+END_SRC
19
+
20
+ * Usage
21
+
22
+ First, we need to require it.
23
+
24
+ #+BEGIN_SRC ruby
25
+ require 'path53'
26
+ #+END_SRC
27
+
28
+ We need a HostedZone to change. This can come from the Route53 API, but for the
29
+ purposes of this example, we'll use a simple fixture.
30
+
31
+ #+BEGIN_SRC ruby
32
+ Zone = Struct.new :id, :name
33
+ zone = Zone.new 'abc123', 'example.com.'
34
+ #+END_SRC
35
+
36
+ The primary feature of =path53= is changesets. Let's create one now:
37
+
38
+ #+BEGIN_SRC ruby
39
+ changes = Path53::ChangeSet.new zone
40
+ #+END_SRC
41
+
42
+ With a changeset, we now create a batch of changes, using a block.
43
+
44
+ #+BEGIN_SRC ruby
45
+ changes.batch do
46
+ add upsert a 'example.com', '127.0.0.1'
47
+ add upsert cname 'www.example.com.', 'example.com'
48
+ end
49
+ #+END_SRC
50
+
51
+ There's a fair bit going on in there, so let's have a look.
52
+
53
+ ~add~ says we want to add a change to this batch. ~remove~ would do the
54
+ opposite.
55
+
56
+ ~upsert~ is what this change should do. ~create~, ~delete~, and ~upsert~ are all
57
+ valid here.
58
+
59
+ ~a~ and ~cname~ describe the type of record we want to change. Any standard DNS
60
+ record type would valid here.
61
+
62
+ In the context of the ~a~ record, ~example.com~ refers to the name of the
63
+ record, and ~127.0.0.1~ refers to the target of that record.
64
+
65
+ Now that we have a batch of changes, simply call ~apply!~ to apply them.
66
+
67
+ #+BEGIN_SRC ruby
68
+ changes.apply!
69
+ #+END_SRC
70
+
71
+ ** Want more?
72
+
73
+ As a convenient shorthand, you can do the following:
74
+
75
+ #+BEGIN_SRC ruby
76
+ Path53.change(zone).batch { add upsert a 'www.example.com', '127.0.0.1' }.apply!
77
+ #+END_SRC
78
+
79
+ If you leave out an action, =path53= will assume you meant to ~upsert~.
80
+ Therefore, this is equivalent to the previous example:
81
+
82
+ #+BEGIN_SRC ruby
83
+ Path53.change(zone).batch { add a 'www.example.com', '127.0.0.1' }.apply!
84
+ #+END_SRC
85
+
86
+ =path53= plays nicely with =aws-sdk=. If you're working with ELBs for instance,
87
+ you can pass a LoadBalancerDescription as a target, and =path53= will do the
88
+ right thing. Therefore, the following is valid:
89
+
90
+ #+BEGIN_SRC ruby
91
+ Path53.change(zone).batch { add a 'www.example.com', MyLoadBalancer }.apply!
92
+ #+END_SRC
93
+
94
+ Alias Targets are supported as well:
95
+
96
+ #+BEGIN_SRC ruby
97
+ Path53.change(zone).batch { add a 'www.example.com', alias_target('zone_id', 'name') }.apply!
98
+ #+END_SRC
99
+
100
+ You may be wondering what's up with the ~remove~ method for changesets. Well, it
101
+ turns out =path53= makes it really easy to cache things.
102
+
103
+ #+BEGIN_SRC ruby
104
+ require 'yaml'
105
+
106
+ changes = Path53.change(zone).batch { add a 'www.example.com', '127.0.0.1' }
107
+
108
+ File.write 'saved-changes', YAML.dump(changes)
109
+
110
+ restored = YAML.load File.read 'saved-changes'
111
+ restored.apply!
112
+ #+END_SRC
113
+
114
+ This is useful when you have a changeset that is expensive to calculate, or you
115
+ want to hold state for some reason.
116
+
117
+ *** Even more?
118
+
119
+ Most methods in =path53= support partial evaluation. The following is valid:
120
+
121
+ #+BEGIN_SRC ruby
122
+ Path53.change(zone).batch do
123
+ www = a 'www.example.com'
124
+ add www.('127.0.0.1')
125
+ end
126
+ #+END_SRC
127
+
128
+ If for some reason you wanted to do that. It's pretty handy in an
129
+ ~each_with_object~ block, as an example.
130
+
131
+ * License
132
+
133
+ ~path53~ is available under the [[https://tldrlegal.com/license/mit-license][MIT License]]. See ~LICENSE.txt~ for the full text.
134
+
135
+ * Contributors
136
+
137
+ - [[https://colstrom.github.io/][Chris Olstrom]] | [[mailto:chris@olstrom.com][e-mail]] | [[https://twitter.com/ChrisOlstrom][Twitter]]
@@ -0,0 +1,85 @@
1
+ require 'aws-sdk'
2
+ require 'contracts'
3
+ require_relative 'features'
4
+
5
+ module Path53
6
+ class ChangeSet
7
+ include ::Contracts::Core
8
+ include ::Contracts::Builtin
9
+ include ::Path53::Feature::AliasTargets
10
+ include ::Path53::Feature::Changes
11
+ include ::Path53::Feature::ResourceRecordSets
12
+
13
+ HostedZone = RespondTo[:id, :name]
14
+ ChangeRequest = ({
15
+ hosted_zone_id: String,
16
+ change_batch: {
17
+ comment: Maybe[String],
18
+ changes: ArrayOf[Change]
19
+ }
20
+ })
21
+
22
+ attr_reader :zone
23
+
24
+ Contract HostedZone => ChangeSet
25
+ def initialize(zone, changes = Set.new)
26
+ @zone = zone
27
+ @changes = changes
28
+ self
29
+ end
30
+
31
+ Contract Change => ChangeSet
32
+ def add(change)
33
+ @changes.add change
34
+ self
35
+ end
36
+
37
+ Contract BoundRecordSet => ChangeSet
38
+ def add(change)
39
+ @changes.add(*changes(change))
40
+ self
41
+ end
42
+
43
+ Contract Change => ChangeSet
44
+ def remove(change)
45
+ @changes.delete change
46
+ self
47
+ end
48
+
49
+ alias rem remove
50
+
51
+ Contract None => ChangeSet
52
+ def reset!
53
+ @changes = Set.new
54
+ self
55
+ end
56
+
57
+ Contract Proc => ChangeSet
58
+ def batch(&block)
59
+ self.tap do |this|
60
+ this.instance_eval(&block)
61
+ end
62
+ end
63
+
64
+ Contract None => String
65
+ def apply!
66
+ route53.change_resource_record_sets(self.to_request).change_info.id
67
+ end
68
+
69
+ Contract KeywordArgs[comment: Optional[String]] => ChangeRequest
70
+ def to_request(comment: nil)
71
+ {
72
+ hosted_zone_id: zone.id,
73
+ change_batch: {
74
+ changes: changes(@changes.to_a)
75
+ }.tap { |batch| batch.merge! comment: comment if comment }
76
+ }
77
+ end
78
+
79
+ private
80
+
81
+ def route53
82
+ @route53 ||= Aws::Route53::Client.new
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,56 @@
1
+ require 'contracts'
2
+
3
+ module Path53
4
+ module Feature
5
+ module AliasTargets
6
+ include ::Contracts::Core
7
+ include ::Contracts::Builtin
8
+
9
+ EvaluateTargetHealth = Or[
10
+ Bool,
11
+ KeywordArgs[evaluate_target_health: Bool],
12
+ ]
13
+
14
+ LoadBalancer = RespondTo[:canonical_hosted_zone_name_id, :canonical_hosted_zone_name]
15
+
16
+ AliasTarget = ({
17
+ hosted_zone_id: String,
18
+ dns_name: String,
19
+ evaluate_target_health: Bool
20
+ })
21
+
22
+ Contract None => Func[EvaluateTargetHealth => Bool]
23
+ def evaluate_target_health
24
+ ->(value) { evaluate_target_health value }
25
+ end
26
+
27
+ Contract Maybe[Bool] => Bool
28
+ def evaluate_target_health(value = false)
29
+ value || false
30
+ end
31
+
32
+ Contract KeywordArgs[evaluate_target_health: Bool] => Bool
33
+ def evaluate_target_health(options)
34
+ evaluate_target_health options.fetch :evaluate_target_health
35
+ end
36
+
37
+ Contract String, String, Maybe[EvaluateTargetHealth] => AliasTarget
38
+ def alias_target(zone, name, check = nil)
39
+ {
40
+ hosted_zone_id: zone,
41
+ dns_name: name,
42
+ evaluate_target_health: evaluate_target_health(check)
43
+ }
44
+ end
45
+
46
+ Contract LoadBalancer, Maybe[EvaluateTargetHealth] => AliasTarget
47
+ def alias_target(load_balancer, check = nil)
48
+ alias_target(
49
+ load_balancer.canonical_hosted_zone_name_id,
50
+ load_balancer.canonical_hosted_zone_name,
51
+ check
52
+ )
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,59 @@
1
+ require 'contracts'
2
+ require_relative 'resource_record_sets'
3
+
4
+ module Path53
5
+ module Feature
6
+ module Changes
7
+ include ::Contracts::Core
8
+ include ::Contracts::Builtin
9
+ include ::Path53::Feature::ResourceRecordSets
10
+
11
+ Action = Enum[*%w(CREATE DELETE UPSERT)]
12
+
13
+ Change = ({
14
+ action: Action,
15
+ resource_record_set: BoundRecordSet
16
+ })
17
+
18
+ ChangeContext = Func[BoundRecordSet => Change]
19
+
20
+ Contract ArrayOf[Change] => ArrayOf[Change]
21
+ def changes(changes)
22
+ changes
23
+ end
24
+
25
+ Contract ArrayOf[BoundRecordSet] => ArrayOf[Change]
26
+ def changes(changes)
27
+ changes.map { |change| upsert change }
28
+ end
29
+
30
+ Contract Or[Change, BoundRecordSet] => ArrayOf[Change]
31
+ def changes(change)
32
+ changes [change]
33
+ end
34
+
35
+ alias change changes
36
+
37
+ def self.included(_)
38
+ Action.instance_variable_get('@vals').each do |action|
39
+ define_method(action.downcase) { |*args| action(action, *args) }
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ Contract Action => ChangeContext
46
+ def action(action)
47
+ ->(record_set) { action action, record_set }
48
+ end
49
+
50
+ Contract Action, BoundRecordSet => Any
51
+ def action(action, record_set)
52
+ {
53
+ action: action,
54
+ resource_record_set: record_set
55
+ }
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,99 @@
1
+ require 'contracts'
2
+ require_relative 'alias_targets'
3
+
4
+ module Path53
5
+ module Feature
6
+ module ResourceRecordSets
7
+ include ::Contracts::Core
8
+ include ::Contracts::Builtin
9
+ include ::Path53::Feature::AliasTargets
10
+
11
+ Type = Enum[*%w(A AAAA CNAME MX NAPTR NS PTR SOA SPF SRV TXT)]
12
+ TTL = Or[Integer, KeywordArgs[ttl: Integer]]
13
+ ResourceRecord = ({ value: String })
14
+
15
+ AliasRecordSet = ({ alias_target: AliasTarget })
16
+ ResourceRecordSet = ({
17
+ resource_records: ArrayOf[ResourceRecord],
18
+ ttl: Integer
19
+ })
20
+
21
+ RecordSet = Or[ResourceRecordSet, AliasRecordSet]
22
+ Target = Xor[RecordSet, LoadBalancer, String]
23
+
24
+ BoundRecordSet = And[RecordSet, ({ name: String, type: Type })]
25
+
26
+ RecordContext = Func[Target => BoundRecordSet]
27
+ TypedContext = Func[String => RecordContext]
28
+
29
+ Contract None => Func[TTL => Integer]
30
+ def ttl
31
+ ->(value) { ttl value }
32
+ end
33
+
34
+ Contract Maybe[Integer] => Integer
35
+ def ttl(value = 300)
36
+ value || 300
37
+ end
38
+
39
+ Contract KeywordArgs[ttl: Integer] => Integer
40
+ def ttl(options)
41
+ ttl options.fetch :ttl
42
+ end
43
+
44
+ Contract ArrayOf[String], Maybe[TTL] => ResourceRecordSet
45
+ def record_set(targets, duration = nil)
46
+ {
47
+ ttl: ttl(duration),
48
+ resource_records: targets.map { |target| { value: name(target) } }
49
+ }
50
+ end
51
+
52
+ Contract String, Maybe[TTL] => ResourceRecordSet
53
+ def record_set(target, duration = nil)
54
+ record_set [target], duration
55
+ end
56
+
57
+ Contract AliasTarget, Maybe[TTL] => AliasRecordSet
58
+ def record_set(target, duration = nil)
59
+ { alias_target: target }
60
+ end
61
+
62
+ Contract LoadBalancer, Maybe[TTL] => AliasRecordSet
63
+ def record_set(load_balancer, duration = nil)
64
+ record_set alias_target load_balancer
65
+ end
66
+
67
+ def self.included(*)
68
+ Type.instance_variable_get('@vals').each do |t|
69
+ define_method(t.downcase) { |*args| type(t, *args) }
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ Contract String => String
76
+ def name(name)
77
+ name.gsub /\.@$/, ".#{zone.name}"
78
+ end
79
+
80
+ Contract Type => TypedContext
81
+ def type(type)
82
+ ->(name, target) { type type, name, target }
83
+ end
84
+
85
+ Contract Type, String => RecordContext
86
+ def type(type, name)
87
+ ->(target) { type type, name, target }
88
+ end
89
+
90
+ Contract Type, String, Target, Maybe[TTL] => BoundRecordSet
91
+ def type(type, name, target, duration = nil)
92
+ {
93
+ type: type,
94
+ name: name(name)
95
+ }.merge record_set(target, duration)
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,3 @@
1
+ require_relative 'feature/alias_targets'
2
+ require_relative 'feature/changes'
3
+ require_relative 'feature/resource_record_sets'
data/lib/path53.rb ADDED
@@ -0,0 +1,7 @@
1
+ require_relative 'path53/change_set'
2
+
3
+ module Path53
4
+ def self.change(zone)
5
+ ChangeSet.new(zone)
6
+ end
7
+ end
data/path53.gemspec ADDED
@@ -0,0 +1,16 @@
1
+ Gem::Specification.new do |gem|
2
+ gem.name = 'path53'
3
+ gem.version = `git describe --tags --abbrev=0`.chomp
4
+ gem.licenses = 'MIT'
5
+ gem.authors = ['Chris Olstrom']
6
+ gem.email = 'chris@olstrom.com'
7
+ gem.homepage = 'https://github.com/colstrom/path53'
8
+ gem.summary = 'Simplified Changes for Route53'
9
+
10
+ gem.files = `git ls-files`.split("\n")
11
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
12
+ gem.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
13
+ gem.require_paths = ['lib']
14
+
15
+ gem.add_runtime_dependency 'aws-sdk', '~> 2.6', '>= 2.6.0'
16
+ end
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: path53
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.3
5
+ platform: ruby
6
+ authors:
7
+ - Chris Olstrom
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-10-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aws-sdk
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.6'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 2.6.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '2.6'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 2.6.0
33
+ description:
34
+ email: chris@olstrom.com
35
+ executables: []
36
+ extensions: []
37
+ extra_rdoc_files: []
38
+ files:
39
+ - ".gitignore"
40
+ - LICENSE.txt
41
+ - README.org
42
+ - lib/path53.rb
43
+ - lib/path53/change_set.rb
44
+ - lib/path53/feature/alias_targets.rb
45
+ - lib/path53/feature/changes.rb
46
+ - lib/path53/feature/resource_record_sets.rb
47
+ - lib/path53/features.rb
48
+ - path53.gemspec
49
+ homepage: https://github.com/colstrom/path53
50
+ licenses:
51
+ - MIT
52
+ metadata: {}
53
+ post_install_message:
54
+ rdoc_options: []
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ requirements: []
68
+ rubyforge_project:
69
+ rubygems_version: 2.6.7
70
+ signing_key:
71
+ specification_version: 4
72
+ summary: Simplified Changes for Route53
73
+ test_files: []