Exemple #1
0
        /// <summary>
        /// Format of the R53 spec is:
        /// <code>
        ///     &lt;zone-id&gt; ';' &lt;record-name&gt; [ ';' [ &lt;record-type&gt; ] [ ';' [ &lt;record-ttl&gt; ] [ ';' &lt;record-value&gt; ] ] ]
        /// </code>
        /// </summary>
        R53Spec ResolveR53Spec(Instance inst, Dictionary <string, string> tags)
        {
            var specTag   = _ec2Eval.Evaluate(tags[R53TriggerTagName], inst);
            var specParts = specTag.Split(";", 5);

            if (specParts.Length < 2)
            {
                throw new Exception("invalid R53 spec");
            }

            if (tags.TryGetValue(R53HealthCheckTriggerTagName, out var healthTag))
            {
                healthTag = _ec2Eval.Evaluate(healthTag, inst);
                _logger.LogInformation($"Adjusting R53 Health Check per resolved spec: {healthTag}");
            }

            _logger.LogInformation($"Adjusting Route53 records per resolved spec: {specTag}");

            var zone = specParts[0];
            var name = specParts[1];

            if (!name.EndsWith("."))
            {
                name += ".";
            }

            var typeRaw = specParts.Length > 2 ? specParts[2] : null;
            var ttlRaw  = specParts.Length > 3 ? specParts[3] : null;
            var valRaw  = specParts.Length > 4 ? specParts[4] : null;

            // Resolve Type or default to A
            var type = string.IsNullOrEmpty(typeRaw) ? null : RRType.FindValue(typeRaw);

            if (type == null)
            {
                _logger.LogInformation("Defaulting to record type `A`");
                type = RRType.A;
            }

            // Resolve TTL or default to 0
            if (!long.TryParse(ttlRaw, out var ttl))
            {
                _logger.LogInformation("Defaulting to TTL 60s");
                ttl = 60;
            }

            // Resolve the record value
            string val = string.IsNullOrEmpty(valRaw)
                ? inst.PublicIpAddress ?? inst.PrivateIpAddress
                : valRaw;

            return(new R53Spec
            {
                Zone = zone,
                Name = name,
                Type = type,
                TTL = ttl,
                Value = val,
            });
        }
Exemple #2
0
        public async Task HandleInitR53(Instance inst, Dictionary <string, string> tags)
        {
            _logger.LogInformation("Handling CREATING R53 records");

            var r53Spec    = ResolveR53Spec(inst, tags);
            var r53Routing = ResolveR53RoutingSpec(inst, tags);

            var rrset = new ResourceRecordSet
            {
                Name            = r53Spec.Name,
                Type            = RRType.FindValue(r53Spec.Type),
                TTL             = r53Spec.TTL,
                ResourceRecords = new List <ResourceRecord>
                {
                    new ResourceRecord(r53Spec.Value),
                },
            };

            // Optional routing policy configuration
            r53Routing?.Apply(rrset);

            var changeRequ = new ChangeResourceRecordSetsRequest
            {
                HostedZoneId = r53Spec.Zone,
                ChangeBatch  = new ChangeBatch
                {
                    Changes = new List <Change>
                    {
                        new Change
                        {
                            Action            = ChangeAction.UPSERT,
                            ResourceRecordSet = rrset,
                        }
                    }
                }
            };

            var changeResp = await _r53.ChangeResourceRecordSetsAsync(changeRequ);

            _logger.LogInformation("UPSERT request completed, response:");
            _logger.LogInformation(JsonSerializer.Serialize(changeResp));
        }
        private async Task <IEnumerable <ResourceRecordSet> > FindRecordSetsAsync(HostedZone zone, string dnsName, string recordType)
        {
            _logger.LogDebug("Finding record sets for {RecordType} {DnsName} in zone {Zone}", recordType, dnsName, zone.Name);

            var result        = new List <ResourceRecordSet>();
            var rootedDnsName = dnsName.EndsWith(DomainSegmentSeparator.ToString()) ? dnsName : dnsName + DomainSegmentSeparator;
            var remainder     = dnsName.Replace(zone.Name, String.Empty);

            var recordSets = await _route53Client.ListResourceRecordSetsAsync(
                new ListResourceRecordSetsRequest()
            {
                HostedZoneId    = zone.Id,
                StartRecordType = RRType.FindValue(recordType),
                StartRecordName = dnsName
            });

            do
            {
                foreach (var recordSet in recordSets.ResourceRecordSets)
                {
                    if (recordSet.Name.ToLower().Equals(rootedDnsName.ToLower()))
                    {
                        result.Add(recordSet);
                    }
                }

                recordSets = await _route53Client.ListResourceRecordSetsAsync(
                    new ListResourceRecordSetsRequest()
                {
                    HostedZoneId          = zone.Id,
                    StartRecordType       = recordSets.NextRecordType,
                    StartRecordName       = recordSets.NextRecordName,
                    StartRecordIdentifier = recordSets.NextRecordIdentifier
                });
            } while (recordSets.IsTruncated);

            _logger.LogInformation("{Count} record sets were found for {RecordType} {DnsName} in zone {Zone}", result.Count, recordType, dnsName, zone.Name);

            return(result);
        }
Exemple #4
0
        public async Task HandleTermR53(Instance inst, Dictionary <string, string> tags)
        {
            _logger.LogInformation("Handling REMOVING R53 records");

            var r53Spec    = ResolveR53Spec(inst, tags);
            var r53Routing = ResolveR53RoutingSpec(inst, tags);

            var rrset = new ResourceRecordSet
            {
                Name            = r53Spec.Name,
                Type            = RRType.FindValue(r53Spec.Type),
                TTL             = r53Spec.TTL,
                ResourceRecords = new List <ResourceRecord>
                {
                    new ResourceRecord(r53Spec.Value),
                },
            };

            // Optional routing policy configuration
            r53Routing?.Apply(rrset);

            var listRequ = new ListResourceRecordSetsRequest
            {
                HostedZoneId          = r53Spec.Zone,
                StartRecordName       = r53Spec.Name,
                StartRecordType       = r53Spec.Type,
                StartRecordIdentifier = null,
            };

            var listResp = await _r53.ListResourceRecordSetsAsync(listRequ);

            var rr = listResp.ResourceRecordSets.FirstOrDefault();

            if (rr == null ||
                rr.Name != r53Spec.Name ||
                rr.Type != r53Spec.Type)
            {
                _logger.LogWarning("No existing resource records found; SKIPPING");
                _logger.LogInformation("First returned record for query:");
                _logger.LogInformation(JsonSerializer.Serialize(rr));
                return;
            }

            var changeRequ = new ChangeResourceRecordSetsRequest
            {
                HostedZoneId = r53Spec.Zone,
                ChangeBatch  = new ChangeBatch
                {
                    Changes = new List <Change>
                    {
                        new Change
                        {
                            Action            = ChangeAction.DELETE,
                            ResourceRecordSet = rrset,
                        }
                    }
                }
            };

            var changeResp = await _r53.ChangeResourceRecordSetsAsync(changeRequ);

            _logger.LogInformation("DELETE request completed, response:");
            _logger.LogInformation(JsonSerializer.Serialize(changeResp));
        }
        protected override async Task PersistAsync(string recordName, string recordType, string recordValue)
        {
            _logger.LogDebug("Starting creation or update of {RecordType} {RecordName} with value {RecordValue}", recordType, recordName, recordValue);

            var zone = await FindHostedZoneAsync(recordName);

            if (zone == null)
            {
                _logger.LogDebug("No zone was found");
                return;
            }

            if (recordType == TxtRecordType)
            {
                recordValue = NormalizeTxtValue(recordValue);
            }

            var existingRecordSets = await FindRecordSetsAsync(zone, recordName, recordType);

            var existingRecordSet = existingRecordSets.FirstOrDefault();

            var recordSet = existingRecordSet ?? new ResourceRecordSet
            {
                Name            = recordName,
                TTL             = 60,
                Type            = RRType.FindValue(recordType),
                ResourceRecords = new List <ResourceRecord>()
            };

            if (recordSet.ResourceRecords.Any(x => x.Value == recordValue))
            {
                _logger.LogDebug("Record {RecordType} {RecordName} with value {RecordValue} already exists", recordType, recordName, recordValue);
                return;
            }

            recordSet.ResourceRecords.Add(new ResourceRecord {
                Value = recordValue
            });

            var change1 = new Change
            {
                ResourceRecordSet = recordSet,
                Action            = ChangeAction.UPSERT
            };

            var changeBatch = new ChangeBatch
            {
                Changes = new List <Change> {
                    change1
                }
            };

            var recordsetRequest = new ChangeResourceRecordSetsRequest
            {
                HostedZoneId = zone.Id,
                ChangeBatch  = changeBatch
            };

            _logger.LogInformation("Creating or updating DNS record {RecordType} {RecordName}", recordType, recordName);

            var upsertResponse = await _route53Client.ChangeResourceRecordSetsAsync(
                new ChangeResourceRecordSetsRequest()
            {
                ChangeBatch  = changeBatch,
                HostedZoneId = zone.Id
            });

            var changeRequest = new GetChangeRequest
            {
                Id = upsertResponse.ChangeInfo.Id
            };

            while (await IsChangePendingAsync(changeRequest))
            {
                _logger.LogDebug("Creation/update of {RecordType} {RecordName} with value {RecordValue} is pending. Checking for status update in {StatusPollIntervalSeconds} seconds.", recordType, recordName, recordValue, StatusPollIntervalSeconds);
                Thread.Sleep(TimeSpan.FromSeconds(StatusPollIntervalSeconds));
            }
        }
Exemple #6
0
        public async Task HandleInitR53(Instance inst, Dictionary <string, string> tags, string ec2State)
        {
            _logger.LogInformation("Handling CREATING R53 records");

            var r53Spec    = ResolveR53RecordSpec(inst, tags);
            var r53Routing = ResolveR53RoutingSpec(inst, tags);
            var r53Health  = await ResolveR53HealthCheckSpec(inst, tags);

            HealthCheck existingHealth = await FindExistingHealthCheck(r53Health);

            // We apply the Health Check if any, first because
            // the subsequent Route policy may depend on it
            string healthCheckId = null;

            if (r53Health != null)
            {
                if (existingHealth == null)
                {
                    var createRequ = new CreateHealthCheckRequest
                    {
                        CallerReference   = r53Health.RefName,
                        HealthCheckConfig = r53Health.Config,
                    };
                    var createResp = await _r53.CreateHealthCheckAsync(createRequ);

                    _logger.LogInformation("CREATE Health Check request completed, response:");
                    _logger.LogInformation(JsonSerializer.Serialize(createResp));
                    if (createResp.HealthCheck == null)
                    {
                        throw new Exception("failed to create Health Check");
                    }
                    healthCheckId = createResp.HealthCheck.Id;

                    var tagRequ = new ChangeTagsForResourceRequest
                    {
                        ResourceType = TagResourceType.Healthcheck,
                        ResourceId   = healthCheckId,
                        AddTags      = new List <Amazon.Route53.Model.Tag>
                        {
                            new Amazon.Route53.Model.Tag
                            {
                                Key   = "Name",
                                Value = r53Health.RefName,
                            },
                            new Amazon.Route53.Model.Tag
                            {
                                Key   = "vmbot:memo",
                                Value = $"Managed by VMBot {nameof(R53Trigger)}",
                            },
                        },
                    };
                    var tagResp = await _r53.ChangeTagsForResourceAsync(tagRequ);

                    _logger.LogInformation("CHANGED TAGS for Health Check:");
                    _logger.LogInformation(JsonSerializer.Serialize(tagResp));
                }
                else
                {
                    var updateRequ = new UpdateHealthCheckRequest
                    {
                        HealthCheckId      = existingHealth.Id,
                        HealthCheckVersion = existingHealth.HealthCheckVersion,
                    };
                    CopyOrReset(existingHealth.HealthCheckConfig, r53Health.Config, updateRequ);
                    _logger.LogInformation("Resolved Health Check delta:");
                    _logger.LogInformation(JsonSerializer.Serialize(updateRequ));
                    var updateResp = await _r53.UpdateHealthCheckAsync(updateRequ);

                    _logger.LogInformation("UPDATE Health Check request completed, response:");
                    _logger.LogInformation(JsonSerializer.Serialize(updateResp));
                    healthCheckId = updateResp.HealthCheck.Id;
                }
            }

            if (r53Spec != null)
            {
                var rrset = new ResourceRecordSet
                {
                    Name            = r53Spec.Name,
                    Type            = RRType.FindValue(r53Spec.Type),
                    TTL             = r53Spec.TTL,
                    ResourceRecords = new List <ResourceRecord>
                    {
                        new ResourceRecord(r53Spec.Value),
                    },
                };
                if (healthCheckId != null)
                {
                    rrset.HealthCheckId = healthCheckId;
                }

                // Optional routing policy configuration
                r53Routing?.Apply(rrset);

                var changeRequ = new ChangeResourceRecordSetsRequest
                {
                    HostedZoneId = r53Spec.Zone,
                    ChangeBatch  = new ChangeBatch
                    {
                        Changes = new List <Change>
                        {
                            new Change
                            {
                                Action            = ChangeAction.UPSERT,
                                ResourceRecordSet = rrset,
                            }
                        },
                        Comment = "change request applied by VMBot",
                    }
                };

                var changeResp = await _r53.ChangeResourceRecordSetsAsync(changeRequ);

                _logger.LogInformation("UPSERT Resource Record request completed, response:");
                _logger.LogInformation(JsonSerializer.Serialize(changeResp));
            }
        }
Exemple #7
0
        public async Task HandleTermR53(Instance inst, Dictionary <string, string> tags, string ec2State)
        {
            _logger.LogInformation("Handling REMOVING R53 records");

            var r53Record  = ResolveR53RecordSpec(inst, tags);
            var r53Routing = ResolveR53RoutingSpec(inst, tags);
            var r53Health  = await ResolveR53HealthCheckSpec(inst, tags);

            HealthCheck existingHealth = await FindExistingHealthCheck(r53Health);

            if (r53Record != null)
            {
                var rrset = new ResourceRecordSet
                {
                    Name            = r53Record.Name,
                    Type            = RRType.FindValue(r53Record.Type),
                    TTL             = r53Record.TTL,
                    ResourceRecords = new List <ResourceRecord>
                    {
                        new ResourceRecord(r53Record.Value),
                    },
                };
                // Optional routing policy configuration
                r53Routing?.Apply(rrset);

                if (existingHealth != null)
                {
                    rrset.HealthCheckId = existingHealth.Id;
                }

                var listRequ = new ListResourceRecordSetsRequest
                {
                    HostedZoneId          = r53Record.Zone,
                    StartRecordName       = r53Record.Name,
                    StartRecordType       = r53Record.Type,
                    StartRecordIdentifier = r53Routing?.SetIdentifier,
                };

                var listResp = await _r53.ListResourceRecordSetsAsync(listRequ);

                var rr = listResp.ResourceRecordSets.FirstOrDefault();

                if (rr == null ||
                    rr.Name != r53Record.Name ||
                    rr.Type != r53Record.Type ||
                    (r53Routing != null && !string.Equals(rr.SetIdentifier, r53Routing.SetIdentifier)))
                {
                    _logger.LogWarning("No existing resource records found; SKIPPING");
                    _logger.LogInformation("First returned record for query:");
                    _logger.LogInformation(JsonSerializer.Serialize(rr));
                }
                else
                {
                    var changeRequ = new ChangeResourceRecordSetsRequest
                    {
                        HostedZoneId = r53Record.Zone,
                        ChangeBatch  = new ChangeBatch
                        {
                            Changes = new List <Change>
                            {
                                new Change
                                {
                                    Action            = ChangeAction.DELETE,
                                    ResourceRecordSet = rrset,
                                }
                            }
                        }
                    };

                    var changeResp = await _r53.ChangeResourceRecordSetsAsync(changeRequ);

                    _logger.LogInformation("DELETE request completed, response:");
                    _logger.LogInformation(JsonSerializer.Serialize(changeResp));
                }
            }

            // We delete the Health Check if any, second because
            // the preceding Route policy may depend on it
            if (existingHealth != null &&
                (ec2State == EC2StateChangeStates.ShuttingDown ||
                 ec2State == EC2StateChangeStates.Terminated))
            {
                if (existingHealth != null)
                {
                    _logger.LogInformation($"Found existing Health Check record [{existingHealth.Id}]");
                    var deleteRequ = new DeleteHealthCheckRequest
                    {
                        HealthCheckId = existingHealth.Id,
                    };
                    var deleteResp = await _r53.DeleteHealthCheckAsync(deleteRequ);

                    _logger.LogInformation("DELETE Health Check request completed, response:");
                    _logger.LogInformation(JsonSerializer.Serialize(deleteResp));
                }
            }
        }