github.com/teknogeek/dnscontrol/v2@v2.10.1-0.20200227202244-ae299b55ba42/docs/adding-new-rtypes.md (about) 1 --- 2 layout: default 3 title: Creating new DNS Resource Types (rtypes) 4 --- 5 6 # Creating new DNS Resource Types (rtypes) 7 8 Everyone is familiar with A, AAAA, CNAME, NS and other Rtypes. 9 However there are new record types being added all the time (possibly 10 too many). Each new record type requires special handling by 11 DNSControl. 12 13 If a record simply has a single "target", then there is little to 14 do because it is handled similarly to A, CNAME, and so on. However 15 if there are multiple fields within the record you have more work 16 to do. 17 18 Our general philosophy is: 19 20 * Internally the individual fields of a record are kept separate. If a particular provider combines them into one big string, that kind of thing is done in the provider code at the end of the food chain. For example, an MX record has a Target (`aspmx.l.google.com.`) and a preference (`10`). Some systems combine this into one string (`10 aspmx.l.google.com.`). We keep the two values separate in `RecordConfig` and leave it up to the individual providers to merge them when required. An earlier implementation kept everything combined and we found ourselves constantly parsing and re-parsing the target. It was inefficient and lead to many bugs. 21 * Anywhere we have a special case for a particular Rtype, we use a `switch` statement and have a `case` for every single record type, usually with a `default:` case that calls `panic()`. This way developers adding a new record type will quickly find where they need to add code (the panic will tell them where). Before we did this, missing implementation code would go unnoticed for months. 22 * Keep things alphabetical. If you are adding your record type to a case statement, function library, or whatever, please list it alphabetically along with the others when possible. 23 24 ## Step 1: Update `RecordConfig` in `models/dns.go` 25 26 If the record has any unique fields, add them to `RecordConfig`. 27 The field name should be the record type, then the field name as 28 used in `github.com/miekg/dns/types.go`. For example, the `CAA` 29 record has a field called `Flag`, therefore the field name in 30 `RecordConfig` is CaaFlag (not `CaaFlags` or `CAAFlags`). 31 32 Here are some examples: 33 34 ``` 35 type RecordConfig struct { 36 ... 37 MxPreference uint16 `json:"mxpreference,omitempty"` // FIXME(tlim): Rename to MxPreference 38 SrvPriority uint16 `json:"srvpriority,omitempty"` 39 SrvWeight uint16 `json:"srvweight,omitempty"` 40 SrvPort uint16 `json:"srvport,omitempty"` 41 CaaTag string `json:"caatag,omitempty"` 42 CaaFlag uint8 `json:"caaflag,omitempty"` 43 ... 44 } 45 ``` 46 47 ## Step 2: Add a capability for the record 48 49 You'll need to mark which providers support this record type. The 50 initial PR should implement this record for the `bind` provider at 51 a minimum, unless this is a fake or pseudo-type that only a particular 52 provider supports. 53 54 * Add the capability to the file `dnscontrol/providers/capabilities.go` (look for `CanUseAlias` and add 55 it to the end of the list.) 56 * Add this feature to the feature matrix in `dnscontrol/build/generate/featureMatrix.go` (Add it to the variable `matrix` then add it later in the file with a `setCap()` statement. 57 * Add the capability to the list of features that zones are validated 58 against (i.e. if you want dnscontrol to report an error if this 59 feature is used with a DNS provider that doesn't support it). That's 60 in the `checkProviderCapabilities` function in 61 `pkg/normalize/validate.go`. 62 * Mark the `bind` provider as supporting this record type by updating `dnscontrol/providers/bind/bindProvider.go` (look for `providers.CanUse` and you'll see what to do). 63 64 DNSControl will warn/error if this new record is used with a 65 provider that does not support the capability. 66 67 * Add the capability to the validations in `pkg/normalize/validate.go` 68 by adding it to `providerCapabilityChecks` 69 * Some capabilities can't be tested for, such as `CanUseTXTMulti`. If 70 such testing can't be done, add it to the whitelist in function 71 `TestCapabilitiesAreFiltered` in 72 `pkg/normalize/capabilities_test.go` 73 74 If the capabilities testing is not configured correctly, `go test ./...` 75 will report something like the `MISSING` message below. In this 76 example we removed `providers.CanUseCAA` from the 77 `providerCapabilityChecks` list. 78 79 ``` 80 --- FAIL: TestCapabilitiesAreFiltered (0.00s) 81 capabilities_test.go:66: ok: providers.CanUseAlias (0) is checked for with "ALIAS" 82 capabilities_test.go:68: MISSING: providers.CanUseCAA (1) is not checked by checkProviderCapabilities 83 capabilities_test.go:66: ok: providers.CanUseNAPTR (3) is checked for with "NAPTR" 84 ``` 85 86 ## Step 3: Add a helper function 87 88 Add a function to `pkg/js/helpers.js` for the new record type. This 89 is the JavaScript file that defines `dnsconfig.js`'s functions like 90 `A()` and `MX()`. Look at the definition of A, MX and CAA for good 91 examples to use as a base. 92 93 Please add the function alphabetically with the others. Also, please run 94 [prettier](https://github.com/prettier/prettier) on the file to ensure 95 your code conforms to our coding standard: 96 97 npm install prettier 98 node_modules/.bin/prettier --write pkg/js/helpers.js 99 100 FYI: If you change `pkg/js/helpers.js`, run `go generate` to update `pkg/js/static.go`. 101 102 ## Step 4: Search for `#rtype_variations` 103 104 Anywhere a rtype requires special handling has been marked with a 105 comment that includes the string `#rtype_variations`. Search for 106 this string and add your new type to this code. 107 108 ## Step 5: Add a `parse_tests` test case. 109 110 Add at least one test case to the `pkg/js/parse_tests` directory. 111 Test `013-mx.js` is a very simple one and is good for cloning. 112 113 Run these tests via: 114 115 cd dnscontrol/pkg/js 116 go test ./... 117 118 If this works, then you know the `dnsconfig.js` and `helpers.js` 119 code is working correctly. 120 121 As you debug, if there are places that haven't been marked 122 `#rtype_variations` that should be, add such a comment. 123 Every time you do this, an angel gets its wings. 124 125 The tests also verify that for every "capability" there is a 126 validation. This is explained in Step 2 (search for 127 `TestCapabilitiesAreFiltered` or `MISSING`) 128 129 ## Step 6: Add an `integrationTest` test case. 130 131 Add at least one test case to the `integrationTest/integration_test.go` 132 file. Look for `var tests =` and add the test to the end of this 133 list. 134 135 Each entry in the list is a new state. For example: 136 137 ``` 138 // MX 139 tc("Empty"), <<< 1 140 tc("MX record", mx("@", 5, "foo.com.")), <<< 2 141 tc("Change MX pref", mx("@", 10, "foo.com.")), <<< 3 142 ``` 143 144 Line 1: An `tc()` entry with no records (just a comment). The test 145 system will delete all records from the domain to make the domain 146 match this empty configuration. This creates a "clean slate" 147 situation. 148 149 Line 2: A `tc()` entry with 1 record. To get to this state, the 150 provider will have to add the record. If this works, basic functionality 151 for the MX record type has been achieved. 152 153 Line 3: A `tc()` entry with 1 record, with a different priority. 154 To get to this state, the provider will have to either change the 155 priority on an existing record, or delete the old record and insert 156 a new one. Either way, this test case assures us that the diff'ing 157 functionality is working properly. 158 159 If you look at the tests for `CAA`, it inserts a few records then 160 attempts to modify each field of a record one at a time. This test 161 was useful because it turns out we hadn't written the code to 162 properly see a change in priority. We fixed this bug before the 163 code made it into production. 164 165 Also notice that some tests include `.IfHasCapability()`. This 166 limits the test to providers with certain capabilities. You'll 167 want to use this feature so that the tests only run on providers 168 that support your new record type. 169 170 To run the integration test with the BIND provider: 171 172 cd dnscontrol/integrationTest 173 go test -v -verbose -provider BIND 174 175 Once the code works for BIND, consider submitting a PR at this point. 176 177 As you debug, if there are places that haven't been marked 178 `#rtype_variations` that should be, add such a comment. 179 If you fail to do this, God kills a cute little kitten. 180 181 ## Step 7: Support more providers 182 183 Now add support other providers. Add the `providers.CanUse...` 184 flag to the provider and re-run the integration tests: 185 186 For example, this will run the tests on Amazon AWS Route53: 187 188 export R53_DOMAIN=dnscontroltest-r53.com # Use a test domain. 189 export R53_KEY_ID=CHANGE_TO_THE_ID 190 export R53_KEY='CHANGE_TO_THE_KEY' 191 go test -v -verbose -provider ROUTE53 192 193 The test should reveal any bugs. Keep iterating between fixing the 194 code and running the tests. When the tests all work, you are done. 195 (Well, you might want to clean up some code a bit, but at least you 196 know that everything is working.) 197 198 If you find bugs that aren't covered by the tests, please please 199 please add a test that demonstrates the bug THEN fix the bug. This 200 will help all future contributors. If you need help with adding 201 tests, please ask!