path53 0.4.3

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
+ 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: []