mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2025-09-26 06:54:43 +08:00
* Stable comparison of metadata (#239) Iterating over a map in Go never produces twice the same ordering. Thus when comparing two metadata map with more than one key, the `differ` is always finding differences. To properly compare records metadata, we need to iterate the maps in a deterministic way. Signed-off-by: Brice Figureau <brice@daysofwonder.com> * Support for Route53 ALIAS record type (#239) Route53 ALIAS doesn't behave like a regular ALIAS, and is much more limited as its target can only be some specific AWS resources or another record in the same zone. According to #239, this change adds a new directive R53_ALIAS which implements this specific alias. This record type can only be used with the Route53 provider. This directive usage looks like this: ```js D("example.com", REGISTRAR, DnsProvider("ROUTE53"), R53_ALIAS("foo1", "A", "bar") // record in same zone R53_ALIAS("foo2", "A", "blahblah.elasticloadbalancing.us-west-1.amazonaws.com", R53_ZONE('Z368ELLRRE2KJ0')) // ELB in us-west-1 ``` Unfortunately, Route53 requires indicating the hosted zone id where the target is defined (those are listed in AWS documentation, see the R53_ALIAS documentation for links).
This commit is contained in:
parent
2fc55dfdc4
commit
7b8d608019
15 changed files with 455 additions and 104 deletions
|
@ -35,6 +35,7 @@ func generateFeatureMatrix() error {
|
|||
{"SRV", "Driver has explicitly implemented SRV record management"},
|
||||
{"TLSA", "Provider can manage TLSA records"},
|
||||
{"TXTMulti", "Provider can manage TXT records with multiple strings"},
|
||||
{"R53_ALIAS", "Provider supports Route 53 limited ALIAS"},
|
||||
|
||||
{"dual host", "This provider is recommended for use in 'dual hosting' scenarios. Usually this means the provider allows full control over the apex NS records"},
|
||||
{"create-domains", "This means the provider can automatically create domains that do not currently exist on your account. The 'dnscontrol create-domains' command will initialize any missing domains"},
|
||||
|
@ -75,6 +76,7 @@ func generateFeatureMatrix() error {
|
|||
setCap("SRV", providers.CanUseSRV)
|
||||
setCap("TLSA", providers.CanUseTLSA)
|
||||
setCap("TXTMulti", providers.CanUseTXTMulti)
|
||||
setCap("R53_ALIAS", providers.CanUseRoute53Alias)
|
||||
setDoc("dual host", providers.DocDualHost, false)
|
||||
setDoc("create-domains", providers.DocCreateDomains, true)
|
||||
|
||||
|
|
47
docs/_functions/domain/R53_ALIAS.md
Normal file
47
docs/_functions/domain/R53_ALIAS.md
Normal file
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
name: R53_ALIAS
|
||||
parameters:
|
||||
- name
|
||||
- target
|
||||
- ZONE_ID modifier
|
||||
---
|
||||
|
||||
R53_ALIAS is a Route53 specific virtual record type that points a record at either another record or an AWS entity (like a Cloudfront distribution, an ELB, etc...). It is analagous to a CNAME, but is usually resolved at request-time and served as an A record. Unlike CNAMEs, ALIAS records can be used at the zone apex (`@`)
|
||||
|
||||
Unlike the regular ALIAS directive, R53_ALIAS is only supported on Route53. Attempting to use R53_ALIAS on another provider than Route53 will result in an error.
|
||||
|
||||
The name should be the relative label for the domain.
|
||||
|
||||
Target should be a string representing the target. If it is a single label we will assume it is a relative name on the current domain. If it contains *any* dots, it should be a fully qualified domain name, ending with a `.`.
|
||||
|
||||
The Target can be any of:
|
||||
|
||||
* _CloudFront distribution_: in this case specify the domain name that CloudFront assigned whenyou created your distribution (note that your CloudFront distribution must include an alternante domain name that matches the record you're adding)
|
||||
* _Elastic Beanstalk environment_: specify the CNAME attribute for the environment. The environment must have a regionalized domain name. To get the CNAME, you can use either the [AWS Console](http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/customdomains.html), [AWS Elastic Beanstalk API](http://docs.aws.amazon.com/elasticbeanstalk/latest/api/API_DescribeEnvironments.html), or the [AWS CLI](http://docs.aws.amazon.com/cli/latest/reference/elasticbeanstalk/describe-environments.html).
|
||||
* _ELB load balancer_: specify the DNS name that is associated with the load balancer. To get the DNS name you can use either the AWS Console (on the EC2 page, choose Load Balancers, select the right one, choose the description tab), [ELB API](http://docs.aws.amazon.com/elasticloadbalancing/latest/APIReference/API_DescribeLoadBalancers.html), the [AWS ELB CLI](http://docs.aws.amazon.com/cli/latest/reference/elb/describe-load-balancers.html), or the [AWS ELBv2 CLI](http://docs.aws.amazon.com/cli/latest/reference/elbv2/describe-load-balancers.html).
|
||||
* _S3 bucket_ (configured as website): specify the domain name of the Amazon S3 website endpoint in which you configured the bucket (for instance s3-website-us-east-2.amazonaws.com). For the available values refer to the [Amazon S3 Website Endpoints](http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region).
|
||||
* _Another Route53 record_: specify the value of the name of another record in the same hosted zone.
|
||||
|
||||
For all the target type, excluding 'another record', you have to specify the `Zone ID` of the target. This is done by using the `R53_ZONE` record modifier.
|
||||
|
||||
The zone id can be found depending on the target type:
|
||||
|
||||
* _CloudFront distribution_: specify `Z2FDTNDATAQYW2`
|
||||
* _Elastic Beanstalk environment_: specify the hosted zone ID for the region in which the environment has been created. Refer to the [List of regions and hosted Zone IDs](http://docs.aws.amazon.com/general/latest/gr/rande.html#elasticbeanstalk_region).
|
||||
* _ELB load balancer_: specify the value of the hosted zone ID for the load balancer. You can find it in [the List of regions and hosted Zone IDs](http://docs.aws.amazon.com/general/latest/gr/rande.html#elb_region)
|
||||
* _S3 bucket_ (configured as website): specify the hosted zone ID for the region that you created the bucket in. You can find it in [the List of regions and hosted Zone IDs](http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region)
|
||||
* _Another Route 53 record_: you can either specify the correct zone id or do not specify anything and dnscontrol will figure out the right zone id. (Note: Route53 alias can't reference a record in a different zone).
|
||||
|
||||
{% include startExample.html %}
|
||||
{% highlight js %}
|
||||
|
||||
D("example.com", REGISTRAR, DnsProvider("ROUTE53"),
|
||||
R53_ALIAS("foo", "A", "bar"), // record in same zone
|
||||
R53_ALIAS("foo", "A", "bar", R53_ZONE('Z35SXDOTRQ7X7K')), // record in same zone, zone specified
|
||||
R53_ALIAS("foo", "A", "blahblah.elasticloadbalancing.us-west-1.amazonaws.com", R53_ZONE('Z368ELLRRE2KJ0')), // a classic ELB in us-west-1
|
||||
R53_ALIAS("foo", "A", "blahblah.elasticbeanstalk.us-west-2.amazonaws.com", R53_ZONE('Z38NKT9BP95V3O')), // an Elastic Beanstalk environment in us-west-2
|
||||
R53_ALIAS("foo", "A", "blahblah-bucket.s3-website-us-west-1.amazonaws.com", R53_ZONE('Z2F56UZL2M1ACD')), // a website S3 Bucket in us-west-1
|
||||
);
|
||||
|
||||
{%endhighlight%}
|
||||
{% include endExample.html %}
|
11
docs/_functions/record/R53_ZONE.md
Normal file
11
docs/_functions/record/R53_ZONE.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
name: R53_ZONE
|
||||
parameters:
|
||||
- zone_id
|
||||
---
|
||||
|
||||
R53_ZONE sets the required Route53 hosted zone id in a R53_ALIAS record.
|
||||
|
||||
This directive has no impact when used in anything else than a R53_ALIAS.
|
||||
|
||||
Please refer to the R53_ALIAS directive for usage.
|
|
@ -194,7 +194,7 @@
|
|||
<td class="danger">
|
||||
<i class="fa fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td class="danger" data-toggle="tooltip" data-container="body" data-placement="top" title="R53 does not provide a generic ALIAS functionality. They do have 'ALIAS' CNAME types to point at various AWS infrastructure, but dnscontrol has not implemented those.">
|
||||
<td class="danger" data-toggle="tooltip" data-container="body" data-placement="top" title="R53 does not provide a generic ALIAS functionality. Use R53_ALIAS instead.">
|
||||
<i class="fa has-tooltip fa-times text-danger" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
|
@ -376,6 +376,26 @@
|
|||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="Provider supports Route 53 limited ALIAS">R53_ALIAS</th>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td class="success">
|
||||
<i class="fa fa-check text-success" aria-hidden="true"></i>
|
||||
</td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
<td><i class="fa fa-minus dim"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="row-header" style="text-decoration: underline;" data-toggle="tooltip" data-container="body" data-placement="top" title="This provider is recommended for use in 'dual hosting' scenarios. Usually this means the provider allows full control over the apex NS records">dual host</th>
|
||||
<td class="danger" data-toggle="tooltip" data-container="body" data-placement="top" title="This driver does not manage NS records, so should not be used for dual-host scenarios">
|
||||
|
|
|
@ -23,4 +23,5 @@ func init() {
|
|||
2. If you try to use ALIAS records, **all** dns providers for the domain must support ALIAS records. We do not want to serve inconsistent records across providers.
|
||||
3. CNAMEs at `@` are disallowed, but ALIAS is allowed.
|
||||
4. Cloudflare does not have a native ALIAS type, but CNAMEs behave similarly. The Cloudflare provider "rewrites" ALIAS records to CNAME as it sees them. Other providers may not need this step.
|
||||
5. Route 53 requires the use of R53_ALIAS instead of ALIAS.
|
||||
|
||||
|
|
|
@ -107,8 +107,8 @@ func runTests(t *testing.T, prv providers.DNSServiceProvider, domainName string,
|
|||
for _, r := range tst.Records {
|
||||
rc := models.RecordConfig(*r)
|
||||
rc.NameFQDN = dnsutil.AddOrigin(rc.Name, domainName)
|
||||
if rc.Target == "**current-domain**" {
|
||||
rc.Target = domainName + "."
|
||||
if strings.Contains(rc.Target, "**current-domain**") {
|
||||
rc.Target = strings.Replace(rc.Target, "**current-domain**", domainName, 1) + "."
|
||||
}
|
||||
dom.Records = append(dom.Records, &rc)
|
||||
}
|
||||
|
@ -216,6 +216,14 @@ func alias(name, target string) *rec {
|
|||
return makeRec(name, target, "ALIAS")
|
||||
}
|
||||
|
||||
func r53alias(name, aliasType, target string) *rec {
|
||||
r := makeRec(name, target, "R53_ALIAS")
|
||||
r.R53Alias = map[string]string{
|
||||
"type": aliasType,
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func ns(name, target string) *rec {
|
||||
return makeRec(name, target, "NS")
|
||||
}
|
||||
|
@ -506,5 +514,16 @@ func makeTests(t *testing.T) []*TestCase {
|
|||
tc("Add a new record - ignoring foo", a("bar", "1.2.3.4"), ignore("foo")),
|
||||
)
|
||||
|
||||
// R53_ALIAS
|
||||
if !providers.ProviderHasCabability(*providerToRun, providers.CanUseRoute53Alias) {
|
||||
t.Log("Skipping Route53 ALIAS Tests because provider does not support them")
|
||||
} else {
|
||||
tests = append(tests, tc("Empty"),
|
||||
tc("create dependent records", a("foo", "1.2.3.4"), a("quux", "2.3.4.5")),
|
||||
tc("ALIAS to A record in same zone", a("foo", "1.2.3.4"), a("quux", "2.3.4.5"), r53alias("bar", "A", "foo.**current-domain**")),
|
||||
tc("change it", a("foo", "1.2.3.4"), a("quux", "2.3.4.5"), r53alias("bar", "A", "quux.**current-domain**")),
|
||||
)
|
||||
}
|
||||
|
||||
return tests
|
||||
}
|
||||
|
|
|
@ -107,6 +107,7 @@ type RecordConfig struct {
|
|||
TlsaSelector uint8 `json:"tlsaselector,omitempty"`
|
||||
TlsaMatchingType uint8 `json:"tlsamatchingtype,omitempty"`
|
||||
TxtStrings []string `json:"txtstrings,omitempty"` // TxtStrings stores all strings (including the first). Target stores only the first one.
|
||||
R53Alias map[string]string `json:"r53_alias,omitempty"`
|
||||
|
||||
CombinedTarget bool `json:"-"`
|
||||
|
||||
|
@ -132,6 +133,8 @@ func (rc *RecordConfig) String() (content string) {
|
|||
content += fmt.Sprintf(" tlsausage=%d tlsaselector=%d tlsamatchingtype=%d", rc.TlsaUsage, rc.TlsaSelector, rc.TlsaMatchingType)
|
||||
case "CAA":
|
||||
content += fmt.Sprintf(" caatag=%s caaflag=%d", rc.CaaTag, rc.CaaFlag)
|
||||
case "R53_ALIAS":
|
||||
content += fmt.Sprintf(" type=%s zone_id=%s", rc.R53Alias["type"], rc.R53Alias["zone_id"])
|
||||
default:
|
||||
msg := fmt.Sprintf("rc.String rtype %v unimplemented", rc.Type)
|
||||
panic(msg)
|
||||
|
@ -386,7 +389,7 @@ func (dc *DomainConfig) Punycode() error {
|
|||
return err
|
||||
}
|
||||
switch rec.Type { // #rtype_variations
|
||||
case "ALIAS", "MX", "NS", "CNAME", "PTR", "SRV", "URL", "URL301", "FRAME":
|
||||
case "ALIAS", "MX", "NS", "CNAME", "PTR", "SRV", "URL", "URL301", "FRAME", "R53_ALIAS":
|
||||
rec.Target, err = idna.ToASCII(rec.Target)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -165,6 +165,38 @@ var AAAA = recordBuilder('AAAA');
|
|||
// ALIAS(name,target, recordModifiers...)
|
||||
var ALIAS = recordBuilder('ALIAS');
|
||||
|
||||
// R53_ALIAS(name, target, type, recordModifiers...)
|
||||
var R53_ALIAS = recordBuilder('R53_ALIAS', {
|
||||
args: [['name', _.isString], ['type', validateR53AliasType], ['target', _.isString]],
|
||||
transform: function (record, args, modifiers) {
|
||||
record.name = args.name;
|
||||
record.target = args.target;
|
||||
if (_.isObject(record.r53_alias)) {
|
||||
record.r53_alias['type'] = args.type;
|
||||
} else {
|
||||
record.r53_alias = { 'type': args.type };
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// R53_ZONE(zone_id)
|
||||
function R53_ZONE(zone_id) {
|
||||
return function (r) {
|
||||
if (_.isObject(r.r53_alias)) {
|
||||
r.r53_alias['zone_id'] = zone_id;
|
||||
} else {
|
||||
r.r53_alias = { 'zone_id': zone_id }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function validateR53AliasType(value) {
|
||||
if (!_.isString(value)) {
|
||||
return false;
|
||||
}
|
||||
return ['A', 'AAAA', 'CNAME', 'CAA', 'MX', 'TXT', 'PTR', 'SPF', 'SRV', 'NAPTR'].indexOf(value) != -1;
|
||||
}
|
||||
|
||||
// CAA(name,tag,value, recordModifiers...)
|
||||
var CAA = recordBuilder('CAA', {
|
||||
// TODO(tlim): It should be an error if value is not 0 or 128.
|
||||
|
|
14
pkg/js/parse_tests/019-r53-alias.js
Normal file
14
pkg/js/parse_tests/019-r53-alias.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
D("foo.com", "none",
|
||||
R53_ALIAS("mxtest", "MX", "foo.com."),
|
||||
R53_ALIAS("atest", "A", "foo.com."),
|
||||
R53_ALIAS("atest", "A", "foo.com.", R53_ZONE("Z2FTEDLFRTF")),
|
||||
R53_ALIAS("aaaatest", "AAAA", "foo.com."),
|
||||
R53_ALIAS("aaaatest", "AAAA", "foo.com.", R53_ZONE("ERERTFGFGF")),
|
||||
R53_ALIAS("cnametest", "CNAME", "foo.com."),
|
||||
R53_ALIAS("ptrtest", "PTR", "foo.com."),
|
||||
R53_ALIAS("txttest", "TXT", "foo.com."),
|
||||
R53_ALIAS("srvtest", "SRV", "foo.com."),
|
||||
R53_ALIAS("spftest", "SPF", "foo.com."),
|
||||
R53_ALIAS("caatest", "CAA", "foo.com."),
|
||||
R53_ALIAS("naptrtest", "NAPTR", "foo.com.")
|
||||
);
|
111
pkg/js/parse_tests/019-r53-alias.json
Normal file
111
pkg/js/parse_tests/019-r53-alias.json
Normal file
|
@ -0,0 +1,111 @@
|
|||
{
|
||||
"registrars": [],
|
||||
"dns_providers": [],
|
||||
"domains": [
|
||||
{
|
||||
"name": "foo.com",
|
||||
"registrar": "none",
|
||||
"dnsProviders": {},
|
||||
"records": [
|
||||
{
|
||||
"type": "R53_ALIAS",
|
||||
"name": "mxtest",
|
||||
"target": "foo.com.",
|
||||
"r53_alias": {
|
||||
"type": "MX"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "R53_ALIAS",
|
||||
"name": "atest",
|
||||
"target": "foo.com.",
|
||||
"r53_alias": {
|
||||
"type": "A"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "R53_ALIAS",
|
||||
"name": "atest",
|
||||
"target": "foo.com.",
|
||||
"r53_alias": {
|
||||
"type": "A",
|
||||
"zone_id": "Z2FTEDLFRTF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "R53_ALIAS",
|
||||
"name": "aaaatest",
|
||||
"target": "foo.com.",
|
||||
"r53_alias": {
|
||||
"type": "AAAA"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "R53_ALIAS",
|
||||
"name": "aaaatest",
|
||||
"target": "foo.com.",
|
||||
"r53_alias": {
|
||||
"type": "AAAA",
|
||||
"zone_id": "ERERTFGFGF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "R53_ALIAS",
|
||||
"name": "cnametest",
|
||||
"target": "foo.com.",
|
||||
"r53_alias": {
|
||||
"type": "CNAME"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "R53_ALIAS",
|
||||
"name": "ptrtest",
|
||||
"target": "foo.com.",
|
||||
"r53_alias": {
|
||||
"type": "PTR"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "R53_ALIAS",
|
||||
"name": "txttest",
|
||||
"target": "foo.com.",
|
||||
"r53_alias": {
|
||||
"type": "TXT"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "R53_ALIAS",
|
||||
"name": "srvtest",
|
||||
"target": "foo.com.",
|
||||
"r53_alias": {
|
||||
"type": "SRV"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "R53_ALIAS",
|
||||
"name": "spftest",
|
||||
"target": "foo.com.",
|
||||
"r53_alias": {
|
||||
"type": "SPF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "R53_ALIAS",
|
||||
"name": "caatest",
|
||||
"target": "foo.com.",
|
||||
"r53_alias": {
|
||||
"type": "CAA"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "R53_ALIAS",
|
||||
"name": "naptrtest",
|
||||
"target": "foo.com.",
|
||||
"r53_alias": {
|
||||
"type": "NAPTR"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
167
pkg/js/static.go
167
pkg/js/static.go
|
@ -192,90 +192,93 @@ var _escData = map[string]*_escFile{
|
|||
|
||||
"/helpers.js": {
|
||||
local: "pkg/js/helpers.js",
|
||||
size: 17575,
|
||||
size: 18525,
|
||||
modtime: 0,
|
||||
compressed: `
|
||||
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
|
||||
AP//rK/L5adEAAA=
|
||||
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
|
||||
BLLUrwJ1j3MjweL/BgAA//+VzTYAXUgAAA==
|
||||
`,
|
||||
},
|
||||
|
||||
|
|
|
@ -40,6 +40,9 @@ const (
|
|||
DocDualHost
|
||||
// DocCreateDomains means provider can add domains with the `dnscontrol create-domains` command
|
||||
DocCreateDomains
|
||||
|
||||
// CanUseRoute53Alias indicates the provider support the specific R53_ALIAS records that only the Route53 provider supports
|
||||
CanUseRoute53Alias
|
||||
)
|
||||
|
||||
var providerCapabilities = map[string]map[Capability]bool{}
|
||||
|
|
|
@ -44,7 +44,16 @@ type differ struct {
|
|||
func (d *differ) content(r *models.RecordConfig) string {
|
||||
content := fmt.Sprintf("%v ttl=%d", r.Content(), r.TTL)
|
||||
for _, f := range d.extraValues {
|
||||
for k, v := range f(r) {
|
||||
// sort the extra values map keys to perform a deterministic
|
||||
// comparison since Golang maps iteration order is not guaranteed
|
||||
valueMap := f(r)
|
||||
keys := make([]string, 0)
|
||||
for k := range valueMap {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
v := valueMap[k]
|
||||
content += fmt.Sprintf(" %s=%s", k, v)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -128,6 +128,26 @@ func TestMetaChange(t *testing.T) {
|
|||
checkLengths(t, existing, desired, 0, 0, 0, 1, getMeta)
|
||||
}
|
||||
|
||||
func TestMetaOrdering(t *testing.T) {
|
||||
existing := []*models.RecordConfig{
|
||||
myRecord("www MX 1 1.1.1.1"),
|
||||
}
|
||||
desired := []*models.RecordConfig{
|
||||
myRecord("www MX 1 1.1.1.1"),
|
||||
}
|
||||
existing[0].Metadata["k"] = "aa"
|
||||
existing[0].Metadata["x"] = "cc"
|
||||
desired[0].Metadata["k"] = "aa"
|
||||
desired[0].Metadata["x"] = "cc"
|
||||
checkLengths(t, existing, desired, 1, 0, 0, 0)
|
||||
getMeta := func(r *models.RecordConfig) map[string]string {
|
||||
return map[string]string{
|
||||
"k": r.Metadata["k"],
|
||||
}
|
||||
}
|
||||
checkLengths(t, existing, desired, 1, 0, 0, 0, getMeta)
|
||||
}
|
||||
|
||||
func checkLengths(t *testing.T, existing, desired []*models.RecordConfig, unCount, createCount, delCount, modCount int, valFuncs ...func(*models.RecordConfig) map[string]string) (un, cre, del, mod Changeset) {
|
||||
return checkLengthsWithKeepUnknown(t, existing, desired, unCount, createCount, delCount, modCount, false, valFuncs...)
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ func newRoute53(m map[string]string, metadata json.RawMessage) (*route53Provider
|
|||
}
|
||||
|
||||
var features = providers.DocumentationNotes{
|
||||
providers.CanUseAlias: providers.Cannot("R53 does not provide a generic ALIAS functionality. They do have 'ALIAS' CNAME types to point at various AWS infrastructure, but dnscontrol has not implemented those."),
|
||||
providers.CanUseAlias: providers.Cannot("R53 does not provide a generic ALIAS functionality. Use R53_ALIAS instead."),
|
||||
providers.DocCreateDomains: providers.Can(),
|
||||
providers.DocDualHost: providers.Can(),
|
||||
providers.DocOfficiallySupported: providers.Can(),
|
||||
|
@ -64,6 +64,7 @@ var features = providers.DocumentationNotes{
|
|||
providers.CanUseSRV: providers.Can(),
|
||||
providers.CanUseTXTMulti: providers.Can(),
|
||||
providers.CanUseCAA: providers.Can(),
|
||||
providers.CanUseRoute53Alias: providers.Can(),
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -155,29 +156,48 @@ func (r *route53Provider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode
|
|||
|
||||
var existingRecords = []*models.RecordConfig{}
|
||||
for _, set := range records {
|
||||
for _, rec := range set.ResourceRecords {
|
||||
if *set.Type == "SOA" {
|
||||
continue
|
||||
if set.AliasTarget == nil {
|
||||
for _, rec := range set.ResourceRecords {
|
||||
if *set.Type == "SOA" {
|
||||
continue
|
||||
}
|
||||
r := &models.RecordConfig{
|
||||
NameFQDN: unescape(set.Name),
|
||||
Type: *set.Type,
|
||||
Target: *rec.Value,
|
||||
TTL: uint32(*set.TTL),
|
||||
CombinedTarget: true,
|
||||
}
|
||||
existingRecords = append(existingRecords, r)
|
||||
}
|
||||
} else {
|
||||
r := &models.RecordConfig{
|
||||
NameFQDN: unescape(set.Name),
|
||||
Type: *set.Type,
|
||||
Target: *rec.Value,
|
||||
TTL: uint32(*set.TTL),
|
||||
Type: "R53_ALIAS",
|
||||
Target: aws.StringValue(set.AliasTarget.DNSName),
|
||||
CombinedTarget: true,
|
||||
TTL: 300,
|
||||
R53Alias: map[string]string{
|
||||
"type": *set.Type,
|
||||
"zone_id": *set.AliasTarget.HostedZoneId,
|
||||
},
|
||||
}
|
||||
existingRecords = append(existingRecords, r)
|
||||
}
|
||||
}
|
||||
for _, want := range dc.Records {
|
||||
want.MergeToTarget()
|
||||
// update zone_id to current zone.id if not specified by the user
|
||||
if want.Type == "R53_ALIAS" && want.R53Alias["zone_id"] == "" {
|
||||
want.R53Alias["zone_id"] = getZoneID(zone, want)
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize
|
||||
models.PostProcessRecords(existingRecords)
|
||||
|
||||
// diff
|
||||
differ := diff.New(dc)
|
||||
differ := diff.New(dc, getAliasMap)
|
||||
_, create, delete, modify := differ.IncrementalDiff(existingRecords)
|
||||
|
||||
namesToUpdate := map[key][]string{}
|
||||
|
@ -219,7 +239,7 @@ func (r *route53Provider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode
|
|||
delDesc += strings.Join(namesToUpdate[k], "\n") + "\n"
|
||||
// on delete just submit the original resource set we got from r53.
|
||||
for _, r := range records {
|
||||
if *r.Name == k.Name+"." && *r.Type == k.Type {
|
||||
if *r.Name == k.Name+"." && (*r.Type == k.Type || k.Type == "R53_ALIAS") {
|
||||
rrset = r
|
||||
break
|
||||
}
|
||||
|
@ -230,18 +250,21 @@ func (r *route53Provider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode
|
|||
// on change or create, just build a new record set from our desired state
|
||||
chg.Action = sPtr("UPSERT")
|
||||
rrset = &r53.ResourceRecordSet{
|
||||
Name: sPtr(k.Name),
|
||||
Type: sPtr(k.Type),
|
||||
ResourceRecords: []*r53.ResourceRecord{},
|
||||
Name: sPtr(k.Name),
|
||||
Type: sPtr(k.Type),
|
||||
}
|
||||
for _, r := range recs {
|
||||
val := r.Target
|
||||
rr := &r53.ResourceRecord{
|
||||
Value: &val,
|
||||
if r.Type != "R53_ALIAS" {
|
||||
rr := &r53.ResourceRecord{
|
||||
Value: &val,
|
||||
}
|
||||
rrset.ResourceRecords = append(rrset.ResourceRecords, rr)
|
||||
i := int64(r.TTL)
|
||||
rrset.TTL = &i // TODO: make sure that ttls are consistent within a set
|
||||
} else {
|
||||
rrset = aliasToRRSet(zone, r)
|
||||
}
|
||||
rrset.ResourceRecords = append(rrset.ResourceRecords, rr)
|
||||
i := int64(r.TTL)
|
||||
rrset.TTL = &i // TODO: make sure that ttls are consistent within a set
|
||||
}
|
||||
}
|
||||
chg.ResourceRecordSet = rrset
|
||||
|
@ -279,6 +302,39 @@ func (r *route53Provider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode
|
|||
|
||||
}
|
||||
|
||||
func getAliasMap(r *models.RecordConfig) map[string]string {
|
||||
if r.Type != "R53_ALIAS" {
|
||||
return nil
|
||||
}
|
||||
return r.R53Alias
|
||||
}
|
||||
|
||||
func aliasToRRSet(zone *r53.HostedZone, r *models.RecordConfig) *r53.ResourceRecordSet {
|
||||
rrset := &r53.ResourceRecordSet{
|
||||
Name: sPtr(r.NameFQDN),
|
||||
Type: sPtr(r.R53Alias["type"]),
|
||||
}
|
||||
zoneID := getZoneID(zone, r)
|
||||
targetHealth := false
|
||||
rrset.AliasTarget = &r53.AliasTarget{
|
||||
DNSName: &r.Target,
|
||||
HostedZoneId: aws.String(zoneID),
|
||||
EvaluateTargetHealth: &targetHealth,
|
||||
}
|
||||
return rrset
|
||||
}
|
||||
|
||||
func getZoneID(zone *r53.HostedZone, r *models.RecordConfig) string {
|
||||
zoneID := r.R53Alias["zone_id"]
|
||||
if zoneID == "" {
|
||||
zoneID = aws.StringValue(zone.Id)
|
||||
}
|
||||
if strings.HasPrefix(zoneID, "/hostedzone/") {
|
||||
zoneID = strings.TrimPrefix(zoneID, "/hostedzone/")
|
||||
}
|
||||
return zoneID
|
||||
}
|
||||
|
||||
func (r *route53Provider) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
||||
corrections := []*models.Correction{}
|
||||
actualSet, err := r.getRegistrarNameservers(&dc.Name)
|
||||
|
|
Loading…
Add table
Reference in a new issue