/// <summary> /// Format of the R53 spec is: /// <code> /// <zone-id> ';' <record-name> [ ';' [ <record-type> ] [ ';' [ <record-ttl> ] [ ';' <record-value> ] ] ] /// </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, }); }
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); }
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)); } }
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)); } }
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)); } } }