record_store 0.3.0 → 1.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 +4 -4
- data/.gitignore +3 -9
- data/Gemfile +19 -2
- data/Gemfile.lock +178 -0
- data/{LICENSE.txt → LICENSE} +5 -5
- data/README.md +131 -44
- data/Rakefile +18 -1
- data/bin/record-store +7 -0
- data/bin/setup +8 -2
- data/bin/test +5 -0
- data/circle.yml +8 -0
- data/lib/record_store.rb +48 -104
- data/lib/record_store/changeset.rb +85 -0
- data/lib/record_store/cli.rb +255 -0
- data/lib/record_store/provider.rb +102 -0
- data/lib/record_store/provider/dnsimple.rb +158 -0
- data/lib/record_store/provider/dynect.rb +97 -0
- data/lib/record_store/record.rb +70 -0
- data/lib/record_store/record/a.rb +32 -0
- data/lib/record_store/record/aaaa.rb +32 -0
- data/lib/record_store/record/alias.rb +20 -0
- data/lib/record_store/record/cname.rb +20 -0
- data/lib/record_store/record/mx.rb +25 -0
- data/lib/record_store/record/ns.rb +20 -0
- data/lib/record_store/record/spf.rb +20 -0
- data/lib/record_store/record/srv.rb +27 -0
- data/lib/record_store/record/txt.rb +29 -0
- data/lib/record_store/version.rb +3 -0
- data/lib/record_store/zone.rb +193 -0
- data/lib/record_store/zone/config.rb +24 -0
- data/record_store.gemspec +30 -14
- data/template/Gemfile +3 -0
- data/template/bin/record-store +7 -0
- data/template/bin/setup +3 -0
- data/template/bin/test +5 -0
- data/template/config.yml +5 -0
- data/template/secrets.json +11 -0
- data/template/zones/dnsimple.example.com.yml +37 -0
- data/template/zones/dynect.example.com.yml +37 -0
- metadata +208 -22
- data/.travis.yml +0 -3
- data/bin/console +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 09372c0d086fbae7cc74ca55aee2a955e1a2f761
|
4
|
+
data.tar.gz: 5b7bfedc45cb04ac8170286a608df7c83adcc71f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 89ee26625e1b23999f383762e9ca785f52b9f89572f0b45b72b68e522139dea82dd52cf01f90a7bc62178aeda67d1e0538b07140f43ed139c13226284eead2f0
|
7
|
+
data.tar.gz: f3f01c6a5b40cb4714bc0f147070c6d2590731c688a60fe7ae1ca83366fdaedc205ed7f24f2025a9f26239dbafb4328a3923db021101c1ad17348e4ffc5693c5
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
@@ -1,4 +1,21 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
gem 'thor'
|
4
|
+
gem 'activesupport', '~> 4.2'
|
5
|
+
gem 'activemodel', '~> 4.2'
|
6
|
+
gem 'ejson'
|
7
|
+
|
8
|
+
gem 'fog'
|
9
|
+
gem 'fog-json'
|
10
|
+
gem 'fog-xml'
|
11
|
+
gem 'fog-dynect'
|
12
|
+
|
13
|
+
group :test do
|
14
|
+
gem 'mocha'
|
15
|
+
gem 'vcr'
|
16
|
+
end
|
17
|
+
|
18
|
+
group :development do
|
19
|
+
gem 'pry'
|
20
|
+
gem 'rake'
|
21
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
GEM
|
2
|
+
remote: https://rubygems.org/
|
3
|
+
specs:
|
4
|
+
CFPropertyList (2.3.2)
|
5
|
+
activemodel (4.2.2)
|
6
|
+
activesupport (= 4.2.2)
|
7
|
+
builder (~> 3.1)
|
8
|
+
activesupport (4.2.2)
|
9
|
+
i18n (~> 0.7)
|
10
|
+
json (~> 1.7, >= 1.7.7)
|
11
|
+
minitest (~> 5.1)
|
12
|
+
thread_safe (~> 0.3, >= 0.3.4)
|
13
|
+
tzinfo (~> 1.1)
|
14
|
+
builder (3.2.2)
|
15
|
+
coderay (1.1.0)
|
16
|
+
ejson (1.0.0)
|
17
|
+
excon (0.45.4)
|
18
|
+
fission (0.5.0)
|
19
|
+
CFPropertyList (~> 2.2)
|
20
|
+
fog (1.36.0)
|
21
|
+
fog-aliyun (>= 0.1.0)
|
22
|
+
fog-atmos
|
23
|
+
fog-aws (>= 0.6.0)
|
24
|
+
fog-brightbox (~> 0.4)
|
25
|
+
fog-core (~> 1.32)
|
26
|
+
fog-dynect (~> 0.0.2)
|
27
|
+
fog-ecloud (~> 0.1)
|
28
|
+
fog-google (<= 0.1.0)
|
29
|
+
fog-json
|
30
|
+
fog-local
|
31
|
+
fog-powerdns (>= 0.1.1)
|
32
|
+
fog-profitbricks
|
33
|
+
fog-radosgw (>= 0.0.2)
|
34
|
+
fog-riakcs
|
35
|
+
fog-sakuracloud (>= 0.0.4)
|
36
|
+
fog-serverlove
|
37
|
+
fog-softlayer
|
38
|
+
fog-storm_on_demand
|
39
|
+
fog-terremark
|
40
|
+
fog-vmfusion
|
41
|
+
fog-voxel
|
42
|
+
fog-xenserver
|
43
|
+
fog-xml (~> 0.1.1)
|
44
|
+
ipaddress (~> 0.5)
|
45
|
+
nokogiri (~> 1.5, >= 1.5.11)
|
46
|
+
fog-aliyun (0.1.0)
|
47
|
+
fog-core (~> 1.27)
|
48
|
+
fog-json (~> 1.0)
|
49
|
+
ipaddress (~> 0.8)
|
50
|
+
xml-simple (~> 1.1)
|
51
|
+
fog-atmos (0.1.0)
|
52
|
+
fog-core
|
53
|
+
fog-xml
|
54
|
+
fog-aws (0.7.6)
|
55
|
+
fog-core (~> 1.27)
|
56
|
+
fog-json (~> 1.0)
|
57
|
+
fog-xml (~> 0.1)
|
58
|
+
ipaddress (~> 0.8)
|
59
|
+
fog-brightbox (0.9.0)
|
60
|
+
fog-core (~> 1.22)
|
61
|
+
fog-json
|
62
|
+
inflecto (~> 0.0.2)
|
63
|
+
fog-core (1.32.1)
|
64
|
+
builder
|
65
|
+
excon (~> 0.45)
|
66
|
+
formatador (~> 0.2)
|
67
|
+
mime-types
|
68
|
+
net-scp (~> 1.1)
|
69
|
+
net-ssh (>= 2.1.3)
|
70
|
+
fog-dynect (0.0.2)
|
71
|
+
fog-core
|
72
|
+
fog-json
|
73
|
+
fog-xml
|
74
|
+
fog-ecloud (0.3.0)
|
75
|
+
fog-core
|
76
|
+
fog-xml
|
77
|
+
fog-google (0.1.0)
|
78
|
+
fog-core
|
79
|
+
fog-json
|
80
|
+
fog-xml
|
81
|
+
fog-json (1.0.2)
|
82
|
+
fog-core (~> 1.0)
|
83
|
+
multi_json (~> 1.10)
|
84
|
+
fog-local (0.2.1)
|
85
|
+
fog-core (~> 1.27)
|
86
|
+
fog-powerdns (0.1.1)
|
87
|
+
fog-core (~> 1.27)
|
88
|
+
fog-json (~> 1.0)
|
89
|
+
fog-xml (~> 0.1)
|
90
|
+
fog-profitbricks (0.0.5)
|
91
|
+
fog-core
|
92
|
+
fog-xml
|
93
|
+
nokogiri
|
94
|
+
fog-radosgw (0.0.4)
|
95
|
+
fog-core (>= 1.21.0)
|
96
|
+
fog-json
|
97
|
+
fog-xml (>= 0.0.1)
|
98
|
+
fog-riakcs (0.1.0)
|
99
|
+
fog-core
|
100
|
+
fog-json
|
101
|
+
fog-xml
|
102
|
+
fog-sakuracloud (1.4.0)
|
103
|
+
fog-core
|
104
|
+
fog-json
|
105
|
+
fog-serverlove (0.1.2)
|
106
|
+
fog-core
|
107
|
+
fog-json
|
108
|
+
fog-softlayer (1.0.2)
|
109
|
+
fog-core
|
110
|
+
fog-json
|
111
|
+
fog-storm_on_demand (0.1.1)
|
112
|
+
fog-core
|
113
|
+
fog-json
|
114
|
+
fog-terremark (0.1.0)
|
115
|
+
fog-core
|
116
|
+
fog-xml
|
117
|
+
fog-vmfusion (0.1.0)
|
118
|
+
fission
|
119
|
+
fog-core
|
120
|
+
fog-voxel (0.1.0)
|
121
|
+
fog-core
|
122
|
+
fog-xml
|
123
|
+
fog-xenserver (0.2.2)
|
124
|
+
fog-core
|
125
|
+
fog-xml
|
126
|
+
fog-xml (0.1.2)
|
127
|
+
fog-core
|
128
|
+
nokogiri (~> 1.5, >= 1.5.11)
|
129
|
+
formatador (0.2.5)
|
130
|
+
i18n (0.7.0)
|
131
|
+
inflecto (0.0.2)
|
132
|
+
ipaddress (0.8.0)
|
133
|
+
json (1.8.3)
|
134
|
+
metaclass (0.0.4)
|
135
|
+
method_source (0.8.2)
|
136
|
+
mime-types (2.6.1)
|
137
|
+
mini_portile (0.6.2)
|
138
|
+
minitest (5.9.0)
|
139
|
+
mocha (1.1.0)
|
140
|
+
metaclass (~> 0.0.1)
|
141
|
+
multi_json (1.11.2)
|
142
|
+
net-scp (1.2.1)
|
143
|
+
net-ssh (>= 2.6.5)
|
144
|
+
net-ssh (2.9.2)
|
145
|
+
nokogiri (1.6.6.2)
|
146
|
+
mini_portile (~> 0.6.0)
|
147
|
+
pry (0.10.1)
|
148
|
+
coderay (~> 1.1.0)
|
149
|
+
method_source (~> 0.8.1)
|
150
|
+
slop (~> 3.4)
|
151
|
+
rake (10.4.2)
|
152
|
+
slop (3.6.0)
|
153
|
+
thor (0.19.1)
|
154
|
+
thread_safe (0.3.5)
|
155
|
+
tzinfo (1.2.2)
|
156
|
+
thread_safe (~> 0.1)
|
157
|
+
vcr (2.9.3)
|
158
|
+
xml-simple (1.1.5)
|
159
|
+
|
160
|
+
PLATFORMS
|
161
|
+
ruby
|
162
|
+
|
163
|
+
DEPENDENCIES
|
164
|
+
activemodel (~> 4.2)
|
165
|
+
activesupport (~> 4.2)
|
166
|
+
ejson
|
167
|
+
fog
|
168
|
+
fog-dynect
|
169
|
+
fog-json
|
170
|
+
fog-xml
|
171
|
+
mocha
|
172
|
+
pry
|
173
|
+
rake
|
174
|
+
thor
|
175
|
+
vcr
|
176
|
+
|
177
|
+
BUNDLED WITH
|
178
|
+
1.11.2
|
data/{LICENSE.txt → LICENSE}
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
The MIT License (MIT)
|
2
2
|
|
3
|
-
Copyright (c)
|
3
|
+
Copyright (c) 2016 Shopify
|
4
4
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
@@ -9,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
9
|
copies of the Software, and to permit persons to whom the Software is
|
10
10
|
furnished to do so, subject to the following conditions:
|
11
11
|
|
12
|
-
The above copyright notice and this permission notice shall be included in
|
13
|
-
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
14
|
|
15
15
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
16
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
17
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
18
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
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
|
-
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
CHANGED
@@ -1,73 +1,160 @@
|
|
1
1
|
# Record Store
|
2
2
|
|
3
|
-
Store
|
3
|
+
Record Store is a tool to manage DNS through a git-based workflow.
|
4
4
|
|
5
|
-
##
|
5
|
+
## Getting Started
|
6
6
|
|
7
|
-
Add this line to your application's Gemfile:
|
8
|
-
|
9
|
-
```ruby
|
10
|
-
gem 'record_store'
|
11
7
|
```
|
8
|
+
record-store apply # Applies the DNS changes
|
9
|
+
record-store assert_empty_diff # Asserts there is no divergence between DynECT & the zone files
|
10
|
+
record-store diff # Displays the DNS differences between the zone files in this repo and production
|
11
|
+
record-store download -n, --name=NAME # Downloads all records from zone and creates YAML zone definition in zones/ e.g. record-store download --name=sho...
|
12
|
+
record-store freeze # Freezes all zones under management to prevent manual edits
|
13
|
+
record-store help [COMMAND] # Describe available commands or one specific command
|
14
|
+
record-store list # Lists out records in YAML zonefiles
|
15
|
+
record-store secrets # Decrypts DynECT credentials
|
16
|
+
record-store sort -n, --name=NAME # Sorts the zonefile alphabetically e.g. record-store sort --name=shopify.io
|
17
|
+
record-store thaw # Thaws all zones under management to allow manual edits
|
18
|
+
record-store validate_all_present # Validates that all the zones that are expected are defined
|
19
|
+
record-store validate_change_size # Validates no more then particular limit of DNS records are removed per zone at a time
|
20
|
+
record-store validate_initial_state # Validates state hasn't diverged since the last deploy
|
21
|
+
record-store validate_records # Validates that all DNS records have valid definitions
|
22
|
+
```
|
23
|
+
|
24
|
+
## Providers
|
25
|
+
|
26
|
+
Below is the list of DNS providers supported by Record Store. PRs [adding more](#adding-new-providers) are welcome.
|
27
|
+
|
28
|
+
### DNSimple
|
29
|
+
|
30
|
+
Record Store uses DNSimple's [v1 API](https://developer.dnsimple.com/v1/). To use DNSimple, you'll need to add the primary user's `email` and `api_token` to `secrets.json`. There's currently no support for 2FA.
|
31
|
+
|
32
|
+
### DynECT
|
33
|
+
|
34
|
+
In order to use DynECT, you'll need to create a user that has the [correct read and write permissions](#dynect-permissions). Add the user's `username` & `password` (i.e. API password) as well as your DynECT `customer` name to `secrets.json`.
|
35
|
+
|
36
|
+
#### Design
|
37
|
+
|
38
|
+
The DynECT provider uses [DynECT's DNS API](https://help.dyn.com/rest-resources/) to sync the YAML zone files. DynECT uses an [update/publish cycle](https://help.dyn.com/understanding-works-api/) in their API which means no changes take place until POSTing to the publish endpoint. This allows us to handle all failures by discarding the changes we attempted to create.
|
39
|
+
|
40
|
+
The DynECT zones managed by Record Store are frozen in DynECT (frozen zones cannot be changed); the deploy process will thaw them so it can make the necessary changes, and refreeze once the deploy process has completed.
|
41
|
+
|
42
|
+
|
43
|
+
#### DynECT permissions
|
44
|
+
|
45
|
+
The permissions required are broken into 2 groups:
|
46
|
+
|
47
|
+
* READ: `RecordGet`, `ZoneGet`
|
48
|
+
* WRITE: `RecordAdd`, `RecordDelete`, `ZonePublish`, `ZoneDiscardChangeset`, `ZoneFreeze`, `ZoneThaw`, `ZoneAddNode`,
|
49
|
+
`ZoneRemoveNode`
|
50
|
+
|
51
|
+
All CI validations only require READ permissions; deplyoing requires a user with READ and WRITE permissions.
|
52
|
+
|
53
|
+
For a breakdown of what each permssion allows read through [DynECT's permissions guide](https://help.dyn.com/user-and-group-permissions/).
|
54
|
+
|
55
|
+
----
|
56
|
+
|
57
|
+
# Architecture
|
58
|
+
|
59
|
+
All CLI commands are defined in [`lib/record_store/cli.rb`](lib/record_store/cli.rb) with [Thor](https://github.com/erikhuda/thor).
|
60
|
+
|
61
|
+
### Zones and Records
|
62
|
+
|
63
|
+
The `Zone` and `Record` models are representations of their DNS equivalents. Both have validations to ensure configurations are RFC compliant. These are specified using `ActiveModel::Validations`.
|
64
|
+
|
65
|
+
Most CLI interactions are through the `Zone` model.
|
66
|
+
|
67
|
+
### Providers
|
68
|
+
|
69
|
+
In order to be provider agnostic, Record Store encapsulates all provider interactions in the `Provider` model and its children. A provider is initialized for each `zone`.
|
12
70
|
|
13
|
-
And then execute:
|
14
71
|
|
15
|
-
|
72
|
+
### Changeset
|
16
73
|
|
17
|
-
|
74
|
+
Changesets are how Record Store knows what updates to make. A `Changeset` is generated by comparing the current records in a zone with the desired final state. A `Changeset` is composed of one or more `Changeset::Change`. Each `Change` is either an `addition`, `removal`, or `update`. Since the ID of records aren't specified in zone files, FQDNs are used to dedup when records can be updated or when new ones need to be created.
|
18
75
|
|
19
|
-
|
76
|
+
When running `bin/record-store apply`, a `Changeset` is generated by comparing the current records in a zone's YAML file with the records the provider defines. A zone's YAML file is always considered the primary source of truth.
|
20
77
|
|
21
|
-
|
78
|
+
----
|
22
79
|
|
23
|
-
|
80
|
+
# Development
|
24
81
|
|
82
|
+
To get started developing on Record Store, run `bin/setup`. This will create a development directory, `dev/`, that mimics what a production directory managing DNS records using Record Store would look like. Use it as a sandbox when developing Record Store.
|
83
|
+
|
84
|
+
### Adding new Providers
|
85
|
+
|
86
|
+
To add a new Provider, create a class inherriting `Provider` in [`lib/record_store/provider/`](lib/record_store/provider/). The [DynECT provider](lib/record_store/provider/dnsimple.rb) is good to use as a reference implementation.
|
87
|
+
|
88
|
+
**Note**: _there's no need to wrap `Provider#apply_changeset` unless it's necessary to do something before/after making changes to a zone._
|
89
|
+
|
90
|
+
Provider API interactions are tested with [VCR](https://github.com/vcr/vcr). To generate the fixtures, update [`test/dummy/secrets.json`](test/dummy/secrets.json) with valid credentials, run the test suite, and change the values back to stub credentials.
|
91
|
+
|
92
|
+
**Important**: _be sure to [filter sensitive data](https://github.com/Shopify/record_store/blob/1ec0d1410cf8bedf79bc63e8e4cdc7cdb0f1019b/test/test_helper.rb#L23-L56) from the fixtures or you're going to have a bad time._
|
93
|
+
|
94
|
+
Outline of [`Provider`](lib/record_store/provider.rb):
|
25
95
|
```ruby
|
26
|
-
class
|
27
|
-
|
28
|
-
|
96
|
+
class Provider
|
97
|
+
# Creates a new record to the zone. It is expected this call modifies external state.
|
98
|
+
#
|
99
|
+
# Arguments:
|
100
|
+
# record - a kind of `Record`
|
101
|
+
def add(record)
|
29
102
|
end
|
30
|
-
end
|
31
|
-
```
|
32
103
|
|
33
|
-
|
104
|
+
# Deletes an existing record from the zone. It is expected this call modifies external state.
|
105
|
+
#
|
106
|
+
# Arguments:
|
107
|
+
# record - a kind of `Record`
|
108
|
+
def remove(record)
|
109
|
+
end
|
34
110
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
111
|
+
# Updates an existing record in the zone. It is expected this call modifies external state.
|
112
|
+
#
|
113
|
+
# Arguments:
|
114
|
+
# id - provider specific ID of record to update
|
115
|
+
# record - a kind of `Record` which the record with `id` should be updated to
|
116
|
+
def update(id, record)
|
117
|
+
end
|
39
118
|
|
40
|
-
|
119
|
+
# Downloads all the records from the provider.
|
120
|
+
#
|
121
|
+
# Returns: an array of `Record` for each record in the provider's zone
|
122
|
+
def retrieve_current_records
|
123
|
+
end
|
41
124
|
|
42
|
-
|
43
|
-
|
44
|
-
|
125
|
+
# Returns an array of the zones managed by provider as strings
|
126
|
+
def zones
|
127
|
+
end
|
45
128
|
|
46
|
-
|
129
|
+
######## NOTE ########
|
130
|
+
# The following methods only need to be implemented if the provider supports the ability to
|
131
|
+
# lock/unlock changes to zones.
|
132
|
+
######################
|
47
133
|
|
48
|
-
|
49
|
-
|
50
|
-
|
134
|
+
# Lock the ability to make any changes to the zone without unlocking it first. It is expected
|
135
|
+
# this call modifies external state.
|
136
|
+
def freeze_zone
|
137
|
+
end
|
138
|
+
|
139
|
+
# Unlocks the zone to allow making changes (see `Provider#freeze_zone`).
|
140
|
+
def thaw
|
141
|
+
end
|
51
142
|
end
|
52
143
|
```
|
53
144
|
|
54
|
-
|
145
|
+
#### Provider-Specific Records
|
55
146
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
147
|
+
For provider-specific records (e.g. `ALIAS`), create the record model in `lib/record_store/record` as any other record. In the provider, extend `self.record_types` and append the custom record types to the `Set` returned by `Provider.record_types` (e.g. [`DNSimple.record_types`](https://github.com/Shopify/record_store/blob/1ec0d1410cf8bedf79bc63e8e4cdc7cdb0f1019b/lib/record_store/provider/dnsimple.rb#L5-L7)).
|
148
|
+
|
149
|
+
#### Secrets
|
150
|
+
|
151
|
+
When adding a new provider, be sure to update the `secrets.json` in [`template/secrets.json`](template/secrets.json) and [`test/dummy/secrets.json`](test/dummy/secrets.json) with the new provider and required fields for the API to work.
|
60
152
|
|
61
|
-
## Development
|
62
153
|
|
63
|
-
|
154
|
+
### Test Changes on Providers
|
64
155
|
|
65
|
-
|
156
|
+
In order to test changes on providers, you're going to need to update `dev/secrets.json` with credentials. **Note**: make sure the credentials are for test zone(s) as the changes specified in the directory **will be applied**.
|
66
157
|
|
67
|
-
|
158
|
+
# Acknowledgements
|
68
159
|
|
69
|
-
|
70
|
-
2. Create your feature branch (`git checkout -b my-new-feature`)
|
71
|
-
3. Commit your changes (`git commit -am 'Add some feature'`)
|
72
|
-
4. Push to the branch (`git push origin my-new-feature`)
|
73
|
-
5. Create a new Pull Request
|
160
|
+
Big thanks to [@pjb3](https://github.com/pjb3) for graciously letting us use the `record_store` gem namespace.
|