Skip to content
This repository was archived by the owner on Dec 19, 2025. It is now read-only.

Commit e3632f5

Browse files
committed
Merge pull request #440 from joesondow/ASGARD-217-Route53-self-service-DNS
ASGARD-217 - Route53 DNS management
2 parents af714bc + eea1073 commit e3632f5

File tree

15 files changed

+1394
-6
lines changed

15 files changed

+1394
-6
lines changed
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
/*
2+
* Copyright 2013 Netflix, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.netflix.asgard
17+
18+
import com.amazonaws.services.route53.model.AliasTarget
19+
import com.amazonaws.services.route53.model.ChangeInfo
20+
import com.amazonaws.services.route53.model.HostedZone
21+
import com.amazonaws.services.route53.model.RRType
22+
import com.amazonaws.services.route53.model.ResourceRecord
23+
import com.amazonaws.services.route53.model.ResourceRecordSet
24+
import com.amazonaws.services.route53.model.ResourceRecordSetFailover
25+
import com.amazonaws.services.route53.model.ResourceRecordSetRegion
26+
import grails.converters.JSON
27+
import grails.converters.XML
28+
29+
/**
30+
* Used to interact with Route53 Hosted Zones for DNS management.
31+
*/
32+
class HostedZoneController {
33+
34+
def awsRoute53Service
35+
36+
static editActions = ['prepareResourceRecordSet']
37+
38+
/**
39+
* Lists all the Route53 DNS hosted zones in the account.
40+
*/
41+
def list() {
42+
Collection<HostedZone> hostedZones = awsRoute53Service.getHostedZones()
43+
withFormat {
44+
html { [hostedZones: hostedZones] }
45+
xml { new XML(hostedZones).render(response) }
46+
json { new JSON(hostedZones).render(response) }
47+
}
48+
}
49+
50+
/**
51+
* Shows the details of one Route53 DNS hosted zone, including the related resource record sets.
52+
*/
53+
def show() {
54+
String hostedZoneIdOrName = params.id
55+
UserContext userContext = UserContext.of(request)
56+
HostedZone hostedZone = awsRoute53Service.getHostedZone(userContext, hostedZoneIdOrName)
57+
if (!hostedZone) {
58+
Requests.renderNotFound('Hosted Zone', hostedZoneIdOrName, this)
59+
return
60+
}
61+
62+
List<ResourceRecordSet> resourceRecordSets = awsRoute53Service.getResourceRecordSets(userContext, hostedZone.id)
63+
resourceRecordSets.sort { it.name }
64+
String deletionWarning = "Really delete Hosted Zone '${hostedZone.id}' with name '${hostedZone.name}' and " +
65+
"its ${resourceRecordSets.size()} resource record set${resourceRecordSets.size() == 1 ? '' : 's'}?" +
66+
(resourceRecordSets.size() ? "\n\nThis cannot be undone and could be dangerous." : '')
67+
Map result = [hostedZone: hostedZone, resourceRecordSets: resourceRecordSets]
68+
Map guiVars = result + [deletionWarning: deletionWarning]
69+
withFormat {
70+
html { guiVars }
71+
xml { new XML(result).render(response) }
72+
json { new JSON(result).render(response) }
73+
}
74+
}
75+
76+
/**
77+
* Displays a form to create a new hosted zone.
78+
*/
79+
def create() {
80+
81+
}
82+
83+
/**
84+
* Handles submission of a create form, to make a new Route53 DNS hosted zone.
85+
*
86+
* @param cmd the command object containing the user parameters for creating the new hosted zone
87+
*/
88+
def save(HostedZoneSaveCommand cmd) {
89+
if (cmd.hasErrors()) {
90+
chain(action: 'create', model: [cmd: cmd], params: params)
91+
return
92+
}
93+
UserContext userContext = UserContext.of(request)
94+
try {
95+
HostedZone hostedZone = awsRoute53Service.createHostedZone(userContext, cmd.name, cmd.comment)
96+
flash.message = "Hosted Zone '${hostedZone.id}' with name '${hostedZone.name}' has been created."
97+
redirect(action: 'show', id: hostedZone.id)
98+
} catch (Exception e) {
99+
flash.message = e.message ?: e.cause?.message
100+
chain(action: 'create', model: [cmd: cmd], params: params)
101+
}
102+
}
103+
104+
/**
105+
* Deletes a Route53 DNS hosted zone, including all of its resource record sets.
106+
*/
107+
def delete = {
108+
UserContext userContext = UserContext.of(request)
109+
String id = params.id
110+
HostedZone hostedZone = awsRoute53Service.getHostedZone(userContext, id)
111+
if (hostedZone) {
112+
ChangeInfo changeInfo = awsRoute53Service.deleteHostedZone(userContext, id)
113+
flash.message = "Deletion of Hosted Zone '${id}' with name '${hostedZone.name}' has started. " +
114+
"ChangeInfo: ${changeInfo}"
115+
redirect([action: 'result'])
116+
} else {
117+
Requests.renderNotFound('Hosted Zone', id, this)
118+
}
119+
}
120+
121+
/**
122+
* Renders a simple page showing the result of a deletion.
123+
*/
124+
def result() { render view: '/common/result' }
125+
126+
/**
127+
* Displays a form to create a new resource record set for the specified Route53 DNS hosted zone.
128+
*/
129+
def prepareResourceRecordSet() {
130+
[
131+
hostedZoneId: params.id ?: params.hostedZoneId,
132+
types: RRType.values()*.toString().sort(),
133+
failoverValues: ResourceRecordSetFailover.values()*.toString().sort(),
134+
resourceRecordSetRegions: ResourceRecordSetRegion.values()*.toString().sort()
135+
]
136+
}
137+
138+
/**
139+
* Creates a new resource record set for an existing Route53 DNS hosted zone.
140+
*
141+
* @param cmd the command object containing all the parameters for creating the new resource record set
142+
*/
143+
def addResourceRecordSet(ResourceRecordSetCommand cmd) {
144+
145+
if (cmd.hasErrors()) {
146+
chain(action: 'prepareResourceRecordSet', model: [cmd: cmd], params: params)
147+
} else {
148+
UserContext userContext = UserContext.of(request)
149+
String id = cmd.hostedZoneId
150+
String comment = cmd.comment
151+
ResourceRecordSet recordSet = resourceRecordSetFromCommandObject(cmd)
152+
try {
153+
ChangeInfo changeInfo = awsRoute53Service.createResourceRecordSet(userContext, id, recordSet, comment)
154+
flash.message = "DNS CREATE change submitted. ChangeInfo: ${changeInfo}"
155+
redirect(action: 'show', id: id)
156+
} catch (Exception e) {
157+
flash.message = "Could not add resource record set: ${e}"
158+
chain(action: 'prepareResourceRecordSet', model: [cmd: cmd], params: params)
159+
}
160+
}
161+
}
162+
163+
/**
164+
* Deletes a resource record set from a Route53 DNS hosted zone.
165+
*
166+
* @param cmd the command object enough parameters to identify and delete a distinct resource record set
167+
*/
168+
def removeResourceRecordSet(ResourceRecordSetCommand cmd) {
169+
if (cmd.hasErrors()) {
170+
chain(action: 'show', id: id)
171+
} else {
172+
UserContext userContext = UserContext.of(request)
173+
String id = cmd.hostedZoneId
174+
String comment = cmd.comment
175+
ResourceRecordSet recordSet = resourceRecordSetFromCommandObject(cmd)
176+
try {
177+
ChangeInfo changeInfo = awsRoute53Service.deleteResourceRecordSet(userContext, id, recordSet, comment)
178+
flash.message = "DNS DELETE change submitted. ChangeInfo: ${changeInfo}"
179+
} catch (Exception e) {
180+
flash.message = "Could not delete resource record set: ${e}"
181+
}
182+
redirect(action: 'show', id: id)
183+
}
184+
}
185+
186+
private resourceRecordSetFromCommandObject(ResourceRecordSetCommand cmd) {
187+
String hostedZoneId = cmd.hostedZoneId
188+
List<String> resourceRecordStrings = Requests.ensureList(cmd.resourceRecords?.split('\n')).collect { it.trim() }
189+
String aliasTarget = cmd.aliasTarget
190+
new ResourceRecordSet(
191+
name: cmd.resourceRecordSetName,
192+
type: cmd.type,
193+
setIdentifier: cmd.setIdentifier ?: null,
194+
weight: cmd.weight ?: null,
195+
region: cmd.resourceRecordSetRegion ?: null,
196+
failover: cmd.failover ?: null,
197+
tTL: cmd.ttl ?: null,
198+
resourceRecords: resourceRecordStrings.collect { new ResourceRecord(it) } ?: null,
199+
aliasTarget: aliasTarget ? new AliasTarget(hostedZoneId, aliasTarget) : null,
200+
healthCheckId: cmd.healthCheckId ?: null
201+
)
202+
}
203+
}
204+
205+
/**
206+
* User parameters for creating a new hosted zone.
207+
*/
208+
class HostedZoneSaveCommand {
209+
String name
210+
String comment
211+
}
212+
213+
/**
214+
* The parameters for creating or deleting a resource record set.
215+
*/
216+
class ResourceRecordSetCommand {
217+
String hostedZoneId
218+
String resourceRecordSetName
219+
String type // From enum RRType
220+
String setIdentifier
221+
Long weight
222+
String resourceRecordSetRegion // From enum ResourceRecordSetRegion
223+
String failover // From ResourceRecordSetFailover
224+
Long ttl
225+
String resourceRecords
226+
String aliasTarget
227+
String healthCheckId
228+
String comment
229+
}

grails-app/services/com/netflix/asgard/AwsClientService.groovy

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import com.amazonaws.services.cloudwatch.AmazonCloudWatchClient
2121
import com.amazonaws.services.ec2.AmazonEC2Client
2222
import com.amazonaws.services.elasticloadbalancing.AmazonElasticLoadBalancingClient
2323
import com.amazonaws.services.rds.AmazonRDSClient
24+
import com.amazonaws.services.route53.AmazonRoute53Client
2425
import com.amazonaws.services.s3.AmazonS3Client
2526
import com.amazonaws.services.simpledb.AmazonSimpleDBClient
2627
import com.amazonaws.services.simpleworkflow.AmazonSimpleWorkflowClient
@@ -31,6 +32,7 @@ import com.netflix.asgard.mock.MockAmazonCloudWatchClient
3132
import com.netflix.asgard.mock.MockAmazonEC2Client
3233
import com.netflix.asgard.mock.MockAmazonElasticLoadBalancingClient
3334
import com.netflix.asgard.mock.MockAmazonRDSClient
35+
import com.netflix.asgard.mock.MockAmazonRoute53Client
3436
import com.netflix.asgard.mock.MockAmazonS3Client
3537
import com.netflix.asgard.mock.MockAmazonSimpleDBClient
3638
import com.netflix.asgard.mock.MockAmazonSimpleWorkflowClient
@@ -66,6 +68,7 @@ class AwsClientService implements InitializingBean {
6668
AmazonElasticLoadBalancing: concrete(AmazonElasticLoadBalancingClient,
6769
MockAmazonElasticLoadBalancingClient),
6870
AmazonRDS: concrete(AmazonRDSClient, MockAmazonRDSClient),
71+
AmazonRoute53: concrete(AmazonRoute53Client, MockAmazonRoute53Client),
6972
AmazonS3: concrete(AmazonS3Client, MockAmazonS3Client),
7073
AmazonSimpleDB: concrete(AmazonSimpleDBClient, MockAmazonSimpleDBClient),
7174
AmazonSimpleWorkflow: concrete(AmazonSimpleWorkflowClient, MockAmazonSimpleWorkflowClient),

0 commit comments

Comments
 (0)