diff --git a/grails-app/controllers/com/netflix/asgard/HostedZoneController.groovy b/grails-app/controllers/com/netflix/asgard/HostedZoneController.groovy new file mode 100644 index 00000000..c1544f54 --- /dev/null +++ b/grails-app/controllers/com/netflix/asgard/HostedZoneController.groovy @@ -0,0 +1,229 @@ +/* + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.netflix.asgard + +import com.amazonaws.services.route53.model.AliasTarget +import com.amazonaws.services.route53.model.ChangeInfo +import com.amazonaws.services.route53.model.HostedZone +import com.amazonaws.services.route53.model.RRType +import com.amazonaws.services.route53.model.ResourceRecord +import com.amazonaws.services.route53.model.ResourceRecordSet +import com.amazonaws.services.route53.model.ResourceRecordSetFailover +import com.amazonaws.services.route53.model.ResourceRecordSetRegion +import grails.converters.JSON +import grails.converters.XML + +/** + * Used to interact with Route53 Hosted Zones for DNS management. + */ +class HostedZoneController { + + def awsRoute53Service + + static editActions = ['prepareResourceRecordSet'] + + /** + * Lists all the Route53 DNS hosted zones in the account. + */ + def list() { + Collection hostedZones = awsRoute53Service.getHostedZones() + withFormat { + html { [hostedZones: hostedZones] } + xml { new XML(hostedZones).render(response) } + json { new JSON(hostedZones).render(response) } + } + } + + /** + * Shows the details of one Route53 DNS hosted zone, including the related resource record sets. + */ + def show() { + String hostedZoneIdOrName = params.id + UserContext userContext = UserContext.of(request) + HostedZone hostedZone = awsRoute53Service.getHostedZone(userContext, hostedZoneIdOrName) + if (!hostedZone) { + Requests.renderNotFound('Hosted Zone', hostedZoneIdOrName, this) + return + } + + List resourceRecordSets = awsRoute53Service.getResourceRecordSets(userContext, hostedZone.id) + resourceRecordSets.sort { it.name } + String deletionWarning = "Really delete Hosted Zone '${hostedZone.id}' with name '${hostedZone.name}' and " + + "its ${resourceRecordSets.size()} resource record set${resourceRecordSets.size() == 1 ? '' : 's'}?" + + (resourceRecordSets.size() ? "\n\nThis cannot be undone and could be dangerous." : '') + Map result = [hostedZone: hostedZone, resourceRecordSets: resourceRecordSets] + Map guiVars = result + [deletionWarning: deletionWarning] + withFormat { + html { guiVars } + xml { new XML(result).render(response) } + json { new JSON(result).render(response) } + } + } + + /** + * Displays a form to create a new hosted zone. + */ + def create() { + + } + + /** + * Handles submission of a create form, to make a new Route53 DNS hosted zone. + * + * @param cmd the command object containing the user parameters for creating the new hosted zone + */ + def save(HostedZoneSaveCommand cmd) { + if (cmd.hasErrors()) { + chain(action: 'create', model: [cmd: cmd], params: params) + return + } + UserContext userContext = UserContext.of(request) + try { + HostedZone hostedZone = awsRoute53Service.createHostedZone(userContext, cmd.name, cmd.comment) + flash.message = "Hosted Zone '${hostedZone.id}' with name '${hostedZone.name}' has been created." + redirect(action: 'show', id: hostedZone.id) + } catch (Exception e) { + flash.message = e.message ?: e.cause?.message + chain(action: 'create', model: [cmd: cmd], params: params) + } + } + + /** + * Deletes a Route53 DNS hosted zone, including all of its resource record sets. + */ + def delete = { + UserContext userContext = UserContext.of(request) + String id = params.id + HostedZone hostedZone = awsRoute53Service.getHostedZone(userContext, id) + if (hostedZone) { + ChangeInfo changeInfo = awsRoute53Service.deleteHostedZone(userContext, id) + flash.message = "Deletion of Hosted Zone '${id}' with name '${hostedZone.name}' has started. " + + "ChangeInfo: ${changeInfo}" + redirect([action: 'result']) + } else { + Requests.renderNotFound('Hosted Zone', id, this) + } + } + + /** + * Renders a simple page showing the result of a deletion. + */ + def result() { render view: '/common/result' } + + /** + * Displays a form to create a new resource record set for the specified Route53 DNS hosted zone. + */ + def prepareResourceRecordSet() { + [ + hostedZoneId: params.id ?: params.hostedZoneId, + types: RRType.values()*.toString().sort(), + failoverValues: ResourceRecordSetFailover.values()*.toString().sort(), + resourceRecordSetRegions: ResourceRecordSetRegion.values()*.toString().sort() + ] + } + + /** + * Creates a new resource record set for an existing Route53 DNS hosted zone. + * + * @param cmd the command object containing all the parameters for creating the new resource record set + */ + def addResourceRecordSet(ResourceRecordSetCommand cmd) { + + if (cmd.hasErrors()) { + chain(action: 'prepareResourceRecordSet', model: [cmd: cmd], params: params) + } else { + UserContext userContext = UserContext.of(request) + String id = cmd.hostedZoneId + String comment = cmd.comment + ResourceRecordSet recordSet = resourceRecordSetFromCommandObject(cmd) + try { + ChangeInfo changeInfo = awsRoute53Service.createResourceRecordSet(userContext, id, recordSet, comment) + flash.message = "DNS CREATE change submitted. ChangeInfo: ${changeInfo}" + redirect(action: 'show', id: id) + } catch (Exception e) { + flash.message = "Could not add resource record set: ${e}" + chain(action: 'prepareResourceRecordSet', model: [cmd: cmd], params: params) + } + } + } + + /** + * Deletes a resource record set from a Route53 DNS hosted zone. + * + * @param cmd the command object enough parameters to identify and delete a distinct resource record set + */ + def removeResourceRecordSet(ResourceRecordSetCommand cmd) { + if (cmd.hasErrors()) { + chain(action: 'show', id: id) + } else { + UserContext userContext = UserContext.of(request) + String id = cmd.hostedZoneId + String comment = cmd.comment + ResourceRecordSet recordSet = resourceRecordSetFromCommandObject(cmd) + try { + ChangeInfo changeInfo = awsRoute53Service.deleteResourceRecordSet(userContext, id, recordSet, comment) + flash.message = "DNS DELETE change submitted. ChangeInfo: ${changeInfo}" + } catch (Exception e) { + flash.message = "Could not delete resource record set: ${e}" + } + redirect(action: 'show', id: id) + } + } + + private resourceRecordSetFromCommandObject(ResourceRecordSetCommand cmd) { + String hostedZoneId = cmd.hostedZoneId + List resourceRecordStrings = Requests.ensureList(cmd.resourceRecords?.split('\n')).collect { it.trim() } + String aliasTarget = cmd.aliasTarget + new ResourceRecordSet( + name: cmd.resourceRecordSetName, + type: cmd.type, + setIdentifier: cmd.setIdentifier ?: null, + weight: cmd.weight ?: null, + region: cmd.resourceRecordSetRegion ?: null, + failover: cmd.failover ?: null, + tTL: cmd.ttl ?: null, + resourceRecords: resourceRecordStrings.collect { new ResourceRecord(it) } ?: null, + aliasTarget: aliasTarget ? new AliasTarget(hostedZoneId, aliasTarget) : null, + healthCheckId: cmd.healthCheckId ?: null + ) + } +} + +/** + * User parameters for creating a new hosted zone. + */ +class HostedZoneSaveCommand { + String name + String comment +} + +/** + * The parameters for creating or deleting a resource record set. + */ +class ResourceRecordSetCommand { + String hostedZoneId + String resourceRecordSetName + String type // From enum RRType + String setIdentifier + Long weight + String resourceRecordSetRegion // From enum ResourceRecordSetRegion + String failover // From ResourceRecordSetFailover + Long ttl + String resourceRecords + String aliasTarget + String healthCheckId + String comment +} diff --git a/grails-app/services/com/netflix/asgard/AwsClientService.groovy b/grails-app/services/com/netflix/asgard/AwsClientService.groovy index a71a785d..fd9382e2 100644 --- a/grails-app/services/com/netflix/asgard/AwsClientService.groovy +++ b/grails-app/services/com/netflix/asgard/AwsClientService.groovy @@ -21,6 +21,7 @@ import com.amazonaws.services.cloudwatch.AmazonCloudWatchClient import com.amazonaws.services.ec2.AmazonEC2Client import com.amazonaws.services.elasticloadbalancing.AmazonElasticLoadBalancingClient import com.amazonaws.services.rds.AmazonRDSClient +import com.amazonaws.services.route53.AmazonRoute53Client import com.amazonaws.services.s3.AmazonS3Client import com.amazonaws.services.simpledb.AmazonSimpleDBClient import com.amazonaws.services.simpleworkflow.AmazonSimpleWorkflowClient @@ -31,6 +32,7 @@ import com.netflix.asgard.mock.MockAmazonCloudWatchClient import com.netflix.asgard.mock.MockAmazonEC2Client import com.netflix.asgard.mock.MockAmazonElasticLoadBalancingClient import com.netflix.asgard.mock.MockAmazonRDSClient +import com.netflix.asgard.mock.MockAmazonRoute53Client import com.netflix.asgard.mock.MockAmazonS3Client import com.netflix.asgard.mock.MockAmazonSimpleDBClient import com.netflix.asgard.mock.MockAmazonSimpleWorkflowClient @@ -66,6 +68,7 @@ class AwsClientService implements InitializingBean { AmazonElasticLoadBalancing: concrete(AmazonElasticLoadBalancingClient, MockAmazonElasticLoadBalancingClient), AmazonRDS: concrete(AmazonRDSClient, MockAmazonRDSClient), + AmazonRoute53: concrete(AmazonRoute53Client, MockAmazonRoute53Client), AmazonS3: concrete(AmazonS3Client, MockAmazonS3Client), AmazonSimpleDB: concrete(AmazonSimpleDBClient, MockAmazonSimpleDBClient), AmazonSimpleWorkflow: concrete(AmazonSimpleWorkflowClient, MockAmazonSimpleWorkflowClient), diff --git a/grails-app/services/com/netflix/asgard/AwsRoute53Service.groovy b/grails-app/services/com/netflix/asgard/AwsRoute53Service.groovy new file mode 100644 index 00000000..2e1697d8 --- /dev/null +++ b/grails-app/services/com/netflix/asgard/AwsRoute53Service.groovy @@ -0,0 +1,233 @@ +/* + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.netflix.asgard + +import com.amazonaws.services.route53.AmazonRoute53 +import com.amazonaws.services.route53.model.Change +import com.amazonaws.services.route53.model.ChangeAction +import com.amazonaws.services.route53.model.ChangeBatch +import com.amazonaws.services.route53.model.ChangeInfo +import com.amazonaws.services.route53.model.ChangeResourceRecordSetsRequest +import com.amazonaws.services.route53.model.CreateHostedZoneRequest +import com.amazonaws.services.route53.model.DeleteHostedZoneRequest +import com.amazonaws.services.route53.model.GetHostedZoneRequest +import com.amazonaws.services.route53.model.HostedZone +import com.amazonaws.services.route53.model.HostedZoneConfig +import com.amazonaws.services.route53.model.ListHostedZonesRequest +import com.amazonaws.services.route53.model.ListHostedZonesResult +import com.amazonaws.services.route53.model.ListResourceRecordSetsRequest +import com.amazonaws.services.route53.model.ListResourceRecordSetsResult +import com.amazonaws.services.route53.model.NoSuchHostedZoneException +import com.amazonaws.services.route53.model.ResourceRecordSet +import com.netflix.asgard.cache.CacheInitializer +import com.netflix.asgard.retriever.AwsResultsRetriever +import grails.util.GrailsNameUtils +import org.springframework.beans.factory.InitializingBean + +/** + * Interactions with Amazon's Route53 DNS service to manage hosted zones and their resource record sets. + */ +class AwsRoute53Service implements CacheInitializer, InitializingBean { + + static transactional = false + + AmazonRoute53 awsClient + def awsClientService + Caches caches + def taskService + + void afterPropertiesSet() { + + // Route53 only has one endpoint. + awsClient = awsClient ?: awsClientService.create(AmazonRoute53) + } + + void initializeCaches() { + caches.allHostedZones.ensureSetUp({ Region region -> retrieveHostedZones() }) + } + + /** + * Gets all the hosted zones from the cache. + * + * @return the list of all hosted zones + */ + List getHostedZones() { + caches.allHostedZones.list().sort { it.name } + } + + /** + * Gets one hosted zone based on a string that could be a hosted zone ID that has no dots, or a name that + * resembles a domain name with a dot on the end. + * + * @param userContext who, where, why + * @param idOrName either a hosted zone ID like Z1FLYQKCK7FTD2, or a name ending with a dot like test.example.com. + * or a name with the trailing dot missing like test.example.com + * @return a matching hosted zone, or null if no match found + */ + HostedZone getHostedZone(UserContext userContext, String idOrName) { + // Hosted zone names always contain dots, and hosted zone IDs never contain dots. + // Users may request "test.example.com" for a hosted zone with name "test.example.com." + if (!idOrName) { return null } + String id = idOrName.contains('.') ? (zoneByName(idOrName) ?: zoneByName("${idOrName}."))?.id : idOrName + try { + HostedZone hostedZone = awsClient.getHostedZone(new GetHostedZoneRequest(id: id)).hostedZone + return caches.allHostedZones.put(id, hostedZone) + } catch (NoSuchHostedZoneException ignored) { + return null + } + } + + private HostedZone zoneByName(String name) { + caches.allHostedZones.list().find { it.name == name } + } + + /** + * Creates a new hosted zone. + * + * @param userContext who, where, why + * @param name the name of the hosted zone such as "test.example.com." + * @param comment an optional human-readable explanation of what makes this hosted zone distinctive + * @callerReference the unique id of the change request, defaulting to a random universally unique ID value + * @return the newly created hosted zone + */ + HostedZone createHostedZone(UserContext userContext, String name, String comment, + String callerReference = UUID.randomUUID().toString()) { + HostedZone hostedZone = taskService.runTask(userContext, "Create Hosted Zone '${name}'", { task -> + CreateHostedZoneRequest request = new CreateHostedZoneRequest(name, callerReference) + if (comment) { + request.hostedZoneConfig = new HostedZoneConfig(comment: comment) + } + awsClient.createHostedZone(request).hostedZone + }, Link.to(EntityType.hostedZone, name)) as HostedZone + getHostedZone(userContext, hostedZone.id) + } + + /** + * Deletes a hosted zone. + * + * @param userContext who, where, why + * @param hostedZoneId the ID of the hosted zone such as Z1FLYQKCK7FTD2 + * @return the object that represents the change request including the request ID + */ + ChangeInfo deleteHostedZone(UserContext userContext, String hostedZoneId) { + ChangeInfo changeInfo = taskService.runTask(userContext, "Delete Hosted Zone ${hostedZoneId}", { task -> + DeleteHostedZoneRequest request = new DeleteHostedZoneRequest(hostedZoneId) + awsClient.deleteHostedZone(request).changeInfo + }, Link.to(EntityType.hostedZone, hostedZoneId)) as ChangeInfo + caches.allHostedZones.remove(hostedZoneId) + changeInfo + } + + private AwsResultsRetriever hostedZoneRetriever = new AwsResultsRetriever() { + + ListHostedZonesResult makeRequest(Region region, ListHostedZonesRequest request) { + awsClient.listHostedZones(request) + } + + List accessResult(ListHostedZonesResult result) { + result.hostedZones + } + + protected void setNextToken(ListHostedZonesRequest request, String nextToken) { + request.withMarker(nextToken) + } + + protected String getNextToken(ListHostedZonesResult result) { + result.nextMarker + } + } + + private List retrieveHostedZones() { + hostedZoneRetriever.retrieve(null, new ListHostedZonesRequest()) + } + + private AwsResultsRetriever resourceRecordSetRetriever = new AwsResultsRetriever() { + + ListResourceRecordSetsResult makeRequest(Region region, ListResourceRecordSetsRequest request) { + awsClient.listResourceRecordSets(request) + } + + List accessResult(ListResourceRecordSetsResult result) { + result.resourceRecordSets + } + + protected void setNextToken(ListResourceRecordSetsRequest request, String nextToken) { + request.withStartRecordName(nextToken) + } + + protected String getNextToken(ListResourceRecordSetsResult result) { + result.nextRecordName + } + } + + /** + * Gets all the resource record sets for a specified hosted zone ID. + * + * @param userContext who, where, why + * @param hostedZoneId the ID of the hosted zone such as Z1FLYQKCK7FTD2 + * @return all the DNS resource record sets for the hosted zone + */ + List getResourceRecordSets(UserContext userContext, String hostedZoneId) { + ListResourceRecordSetsRequest request = new ListResourceRecordSetsRequest(hostedZoneId: hostedZoneId) + resourceRecordSetRetriever.retrieve(null, request) + } + + /** + * Creates a new resource record set within a hosted zone. + * + * @param userContext who, where, why + * @param hostedZoneId the ID of the hosted zone such as Z1FLYQKCK7FTD2 + * @param resourceRecordSet the ResourceRecordSet object to create + * @param comment the optional human-readable reason for the change request + * @return info about this change request + */ + ChangeInfo createResourceRecordSet(UserContext userContext, String hostedZoneId, + ResourceRecordSet resourceRecordSet, String comment) { + taskService.runTask(userContext, "Create Resource Record ${resourceRecordSet.name}", { + Change change = new Change(action: ChangeAction.CREATE, resourceRecordSet: resourceRecordSet) + changeResourceRecordSet(userContext, hostedZoneId, change, comment) + }, Link.to(EntityType.hostedZone, hostedZoneId)) as ChangeInfo + } + + /** + * Deletes an existing resource record set from a hosted zone. + * + * @param userContext who, where, why + * @param hostedZoneId the ID of the hosted zone that contains the resource record set + * @param resourceRecordSet the DNS entry to delete + * @param comment the optional human-readable reason for the change request + * @return info about this change request + */ + ChangeInfo deleteResourceRecordSet(UserContext userContext, String hostedZoneId, + ResourceRecordSet resourceRecordSet, String comment) { + taskService.runTask(userContext, "Delete Resource Record ${resourceRecordSet.name}", { + Change change = new Change(action: ChangeAction.DELETE, resourceRecordSet: resourceRecordSet) + changeResourceRecordSet(userContext, hostedZoneId, change, comment) + }, Link.to(EntityType.hostedZone, hostedZoneId)) as ChangeInfo + } + + private ChangeInfo changeResourceRecordSet(UserContext userContext, String hostedZoneId, Change change, String comment) { + ChangeBatch changeBatch = new ChangeBatch(comment: comment, changes: [change]) + def request = new ChangeResourceRecordSetsRequest(hostedZoneId: hostedZoneId, changeBatch: changeBatch) + String action = GrailsNameUtils.getNaturalName(change.action.toLowerCase()) + String msg = "${action} Resource Record ${change.resourceRecordSet.name}" + taskService.runTask(userContext, msg, { + awsClient.changeResourceRecordSets(request).changeInfo + }, Link.to(EntityType.hostedZone, hostedZoneId)) as ChangeInfo + } +} diff --git a/grails-app/services/com/netflix/asgard/Caches.groovy b/grails-app/services/com/netflix/asgard/Caches.groovy index fd0c771c..604cb26f 100644 --- a/grails-app/services/com/netflix/asgard/Caches.groovy +++ b/grails-app/services/com/netflix/asgard/Caches.groovy @@ -36,6 +36,7 @@ import com.amazonaws.services.elasticloadbalancing.model.SourceSecurityGroup import com.amazonaws.services.rds.model.DBInstance import com.amazonaws.services.rds.model.DBSecurityGroup import com.amazonaws.services.rds.model.DBSnapshot +import com.amazonaws.services.route53.model.HostedZone import com.amazonaws.services.simpleworkflow.model.ActivityTypeInfo import com.amazonaws.services.simpleworkflow.model.DomainInfo import com.amazonaws.services.simpleworkflow.model.WorkflowExecutionInfo @@ -62,6 +63,7 @@ class Caches { final CachedMap allApplications final CachedMap allCustomMetrics final CachedMap allHardwareProfiles + final CachedMap allHostedZones final CachedMap allTerminationPolicyTypes final CachedMap allWorkflowTypes final CachedMap allWorkflowDomains @@ -138,6 +140,7 @@ class Caches { allCustomMetrics = cachedMapBuilder.of(EntityType.metric, 120).buildCachedMap() allWorkflowTypes = cachedMapBuilder.of(EntityType.workflowType, 120).buildCachedMap() allWorkflowDomains = cachedMapBuilder.of(EntityType.workflowDomain, 3600).buildCachedMap() + allHostedZones = cachedMapBuilder.of(EntityType.hostedZone, 120).buildCachedMap() // Use one thread for all instance type and pricing caches. None of these need updating more than once an hour. allHardwareProfiles = cachedMapBuilder.of(EntityType.hardwareProfile, 3600).buildCachedMap() diff --git a/grails-app/services/com/netflix/asgard/TaskService.groovy b/grails-app/services/com/netflix/asgard/TaskService.groovy index e61f783e..cb78bcc0 100644 --- a/grails-app/services/com/netflix/asgard/TaskService.groovy +++ b/grails-app/services/com/netflix/asgard/TaskService.groovy @@ -34,8 +34,8 @@ class TaskService { static transactional = false private static final Collection NON_ALERTABLE_ERROR_CODES = ['DBInstanceAlreadyExists', - 'DuplicateLoadBalancerName', 'InvalidDBInstanceState', 'InvalidDBSnapshotState', 'InvalidGroup.Duplicate', - 'InvalidGroup.InUse', 'InvalidParameterValue', 'ValidationError'] + 'DuplicateLoadBalancerName', 'InvalidChangeBatch', 'InvalidDBInstanceState', 'InvalidDBSnapshotState', + 'InvalidGroup.Duplicate', 'InvalidGroup.InUse', 'InvalidInput', 'InvalidParameterValue', 'ValidationError'] Caches caches def awsSimpleWorkflowService diff --git a/grails-app/views/hostedZone/create.gsp b/grails-app/views/hostedZone/create.gsp new file mode 100644 index 00000000..529cb583 --- /dev/null +++ b/grails-app/views/hostedZone/create.gsp @@ -0,0 +1,64 @@ +<%-- + + Copyright 2013 Netflix, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +--%> + + + + + Create New Hosted Zone + + +
+

Create New Hosted Zone

+ +
${flash.message}
+
+ +
+ +
+
+ +
+ + + + + + + + + + + +
+ + + +
+ + + +
+
+
+ Create New Hosted Zone +
+
+
+ + diff --git a/grails-app/views/hostedZone/list.gsp b/grails-app/views/hostedZone/list.gsp new file mode 100644 index 00000000..5e8f0026 --- /dev/null +++ b/grails-app/views/hostedZone/list.gsp @@ -0,0 +1,60 @@ +<%-- + + Copyright 2013 Netflix, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +--%> + + + + + Route53 Hosted Zones + + +
+

Route53 Hosted Zones

+ +
${flash.message}
+
+ +
+
+ Create New Hosted Zone +
+ + + + + + + + + + + + + + + + + + + +
Hosted Zone IDNameResource Record
Set Count
Comment
${hostedZone.name}${hostedZone.resourceRecordSetCount}${hostedZone.config.comment}
+
+
+
+
+ + diff --git a/grails-app/views/hostedZone/prepareResourceRecordSet.gsp b/grails-app/views/hostedZone/prepareResourceRecordSet.gsp new file mode 100644 index 00000000..50c80ffe --- /dev/null +++ b/grails-app/views/hostedZone/prepareResourceRecordSet.gsp @@ -0,0 +1,141 @@ +<%-- + + Copyright 2013 Netflix, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +--%> + + + + + Add Resource Record Set + + +
+

Add Resource Record Set

+ +
${flash.message}
+
+ +
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Hosted Zone:
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+
+
+ + Add Resource Record Set +
+
+
+ + diff --git a/grails-app/views/hostedZone/show.gsp b/grails-app/views/hostedZone/show.gsp new file mode 100644 index 00000000..0f14782d --- /dev/null +++ b/grails-app/views/hostedZone/show.gsp @@ -0,0 +1,137 @@ +<%-- + + Copyright 2013 Netflix, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +--%> + + + + + ${hostedZone.name} Route53 Hosted Zone + + +
+

Route53 Hosted Zone Details

+ +
${flash.message}
+
+
+ + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Hosted Zone ID:${hostedZone.id}
Name:${hostedZone.name}
Caller Reference:${hostedZone.callerReference}
Comment:${hostedZone.config.comment}
Resource Record Set Count:${hostedZone.resourceRecordSetCount}
Resource Record Sets:
+ +
+
+ Create New Resource Record Set +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeNameResource RecordsTTLRegionAlias
Target
Set
ID
WeightFailoverHealth
Check
ID
${resourceRecordSet.type}${resourceRecordSet.name} +
    + +
  • ${resourceRecord.value}
  • +
    +
+
${resourceRecordSet.TTL}${resourceRecordSet.region}${resourceRecordSet.aliasTarget?.dNSName}${resourceRecordSet.setIdentifier}${resourceRecordSet.weight}${resourceRecordSet.failover}${resourceRecordSet.healthCheckId} + + <%-- + There seems to be no simple way to specify a distinct resource record set. + Every field must be specified in order to avoid deleting a similar but incorrect item. + --%> + + + + + + + + + + + + + +
+
+
+
+
+
+
+ + diff --git a/grails-app/views/layouts/main.gsp b/grails-app/views/layouts/main.gsp index ceb9abb6..d15a2568 100644 --- a/grails-app/views/layouts/main.gsp +++ b/grails-app/views/layouts/main.gsp @@ -109,7 +109,13 @@ - +