2
0
mirror of https://github.com/VinylDNS/vinyldns synced 2025-08-22 10:10:12 +00:00

Merge branch 'master' into aravindhr/optimize-batch-change

This commit is contained in:
Nicholas Spadaccino 2024-08-07 10:39:25 -04:00 committed by GitHub
commit e54dc71fe0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 68 additions and 41 deletions

View File

@ -21,6 +21,7 @@ in any way, but do not see your name here, please open a PR to add yourself (in
- Joe Crowe - Joe Crowe
- Jearvon Dharrie - Jearvon Dharrie
- Andrew Dunn - Andrew Dunn
- Josh Edwards
- Ryan Emerle - Ryan Emerle
- David Grizzanti - David Grizzanti
- Alejandro Guirao - Alejandro Guirao
@ -41,8 +42,9 @@ in any way, but do not see your name here, please open a PR to add yourself (in
- Khalid Reid - Khalid Reid
- Timo Schmid - Timo Schmid
- Trent Schmidt - Trent Schmidt
- Nick Spadaccino - Arpit Shah
- Ghafar Shah - Ghafar Shah
- Nick Spadaccino
- Rebecca Star - Rebecca Star
- Jess Stodola - Jess Stodola
- Juan Valencia - Juan Valencia

View File

@ -146,7 +146,8 @@ See the [Contributing Guide](CONTRIBUTING.md).
The current maintainers (people who can merge pull requests) are: The current maintainers (people who can merge pull requests) are:
- Ryan Emerle ([@remerle](https://github.com/remerle)) - Ryan Emerle ([@remerle](https://github.com/remerle))
- Sriram Ramakrishnan ([@sramakr](https://github.com/sramakr)) - Arpit Shah ([@arpit4ever](https://github.com/arpit4ever))
- Nick Spadaccino ([@nspadaccino](https://github.com/nspadaccino))
- Jim Wakemen ([@jwakemen](https://github.com/jwakemen)) - Jim Wakemen ([@jwakemen](https://github.com/jwakemen))
See [AUTHORS.md](AUTHORS.md) for the full list of contributors to VinylDNS. See [AUTHORS.md](AUTHORS.md) for the full list of contributors to VinylDNS.

View File

@ -584,21 +584,19 @@ class RecordSetService(
} yield change } yield change
def listRecordSetChanges( def listRecordSetChanges(
zoneId: Option[String] = None, zoneId: String,
startFrom: Option[Int] = None, startFrom: Option[Int] = None,
maxItems: Int = 100, maxItems: Int = 100,
fqdn: Option[String] = None,
recordType: Option[RecordType] = None,
authPrincipal: AuthPrincipal authPrincipal: AuthPrincipal
): Result[ListRecordSetChangesResponse] = ): Result[ListRecordSetChangesResponse] =
for { for {
zone <- getZone(zoneId.get) zone <- getZone(zoneId)
_ <- canSeeZone(authPrincipal, zone).toResult _ <- canSeeZone(authPrincipal, zone).toResult
recordSetChangesResults <- recordChangeRepository recordSetChangesResults <- recordChangeRepository
.listRecordSetChanges(Some(zone.id), startFrom, maxItems, fqdn, recordType) .listRecordSetChanges(Some(zone.id), startFrom, maxItems, None, None)
.toResult[ListRecordSetChangesResults] .toResult[ListRecordSetChangesResults]
recordSetChangesInfo <- buildRecordSetChangeInfo(recordSetChangesResults.items) recordSetChangesInfo <- buildRecordSetChangeInfo(recordSetChangesResults.items)
} yield ListRecordSetChangesResponse(zoneId.get, recordSetChangesResults, recordSetChangesInfo) } yield ListRecordSetChangesResponse(zoneId, recordSetChangesResults, recordSetChangesInfo)
def listRecordSetChangeHistory( def listRecordSetChangeHistory(
zoneId: Option[String] = None, zoneId: Option[String] = None,

View File

@ -101,11 +101,9 @@ trait RecordSetServiceAlgebra {
): Result[RecordSetChange] ): Result[RecordSetChange]
def listRecordSetChanges( def listRecordSetChanges(
zoneId: Option[String], zoneId: String,
startFrom: Option[Int], startFrom: Option[Int],
maxItems: Int, maxItems: Int,
fqdn: Option[String],
recordType: Option[RecordType],
authPrincipal: AuthPrincipal authPrincipal: AuthPrincipal
): Result[ListRecordSetChangesResponse] ): Result[ListRecordSetChangesResponse]

View File

@ -230,8 +230,8 @@ class RecordSetRoute(
} ~ } ~
path("zones" / Segment / "recordsetchanges") { zoneId => path("zones" / Segment / "recordsetchanges") { zoneId =>
(get & monitor("Endpoint.listRecordSetChanges")) { (get & monitor("Endpoint.listRecordSetChanges")) {
parameters("startFrom".as[Int].?, "maxItems".as[Int].?(DEFAULT_MAX_ITEMS), "fqdn".as[String].?, "recordType".as[String].?) { parameters("startFrom".as[Int].?, "maxItems".as[Int].?(DEFAULT_MAX_ITEMS)) {
(startFrom: Option[Int], maxItems: Int, fqdn: Option[String], _: Option[String]) => (startFrom: Option[Int], maxItems: Int) =>
handleRejections(invalidQueryHandler) { handleRejections(invalidQueryHandler) {
validate( validate(
check = 0 < maxItems && maxItems <= DEFAULT_MAX_ITEMS, check = 0 < maxItems && maxItems <= DEFAULT_MAX_ITEMS,
@ -240,7 +240,7 @@ class RecordSetRoute(
) { ) {
authenticateAndExecute( authenticateAndExecute(
recordSetService recordSetService
.listRecordSetChanges(Some(zoneId), startFrom, maxItems, fqdn, None, _) .listRecordSetChanges(zoneId, startFrom, maxItems, _)
) { changes => ) { changes =>
complete(StatusCodes.OK, changes) complete(StatusCodes.OK, changes)
} }

View File

@ -2,11 +2,11 @@ pyhamcrest==2.0.2
pytz>=2014 pytz>=2014
pytest==6.2.5 pytest==6.2.5
mock==4.0.3 mock==4.0.3
dnspython==2.1.0 dnspython==2.6.1
boto3==1.18.51 boto3==1.18.51
botocore==1.21.51 botocore==1.21.51
requests==2.31.0 requests==2.32.3
pytest-xdist==2.4.0 pytest-xdist==2.4.0
python-dateutil==2.8.2 python-dateutil==2.8.2
filelock==3.2.0 filelock==3.2.0
pytest-custom_exit_code==0.3.0 pytest-custom_exit_code==0.3.0

View File

@ -1937,19 +1937,22 @@ class RecordSetServiceSpec
val completeRecordSetChanges: List[RecordSetChange] = val completeRecordSetChanges: List[RecordSetChange] =
List(pendingCreateAAAA, pendingCreateCNAME, completeCreateAAAA, completeCreateCNAME) List(pendingCreateAAAA, pendingCreateCNAME, completeCreateAAAA, completeCreateCNAME)
doReturn(IO.pure(Some(zoneActive)))
.when(mockZoneRepo)
.getZone(zoneActive.id)
doReturn(IO.pure(ListRecordSetChangesResults(completeRecordSetChanges))) doReturn(IO.pure(ListRecordSetChangesResults(completeRecordSetChanges)))
.when(mockRecordChangeRepo) .when(mockRecordChangeRepo)
.listRecordSetChanges(zoneId = Some(okZone.id), startFrom = None, maxItems = 100, fqdn = None, recordType = None) .listRecordSetChanges(zoneId = Some(zoneActive.id), startFrom = None, maxItems = 100, fqdn = None, recordType = None)
doReturn(IO.pure(ListUsersResults(Seq(okUser), None))) doReturn(IO.pure(ListUsersResults(Seq(okUser), None)))
.when(mockUserRepo) .when(mockUserRepo)
.getUsers(any[Set[String]], any[Option[String]], any[Option[Int]]) .getUsers(any[Set[String]], any[Option[String]], any[Option[Int]])
val result: ListRecordSetChangesResponse = val result: ListRecordSetChangesResponse =
underTest.listRecordSetChanges(Some(okZone.id), authPrincipal = okAuth).value.unsafeRunSync().toOption.get underTest.listRecordSetChanges(zoneActive.id, authPrincipal = okAuth).value.unsafeRunSync().toOption.get
val changesWithName = val changesWithName =
completeRecordSetChanges.map(change => RecordSetChangeInfo(change, Some("ok"))) completeRecordSetChanges.map(change => RecordSetChangeInfo(change, Some("ok")))
val expectedResults = ListRecordSetChangesResponse( val expectedResults = ListRecordSetChangesResponse(
zoneId = okZone.id, zoneId = zoneActive.id,
recordSetChanges = changesWithName, recordSetChanges = changesWithName,
nextId = None, nextId = None,
startFrom = None, startFrom = None,
@ -1996,7 +1999,7 @@ class RecordSetServiceSpec
.getUsers(any[Set[String]], any[Option[String]], any[Option[Int]]) .getUsers(any[Set[String]], any[Option[String]], any[Option[Int]])
val result: ListRecordSetChangesResponse = val result: ListRecordSetChangesResponse =
underTest.listRecordSetChanges(Some(okZone.id), authPrincipal = okAuth).value.unsafeRunSync().toOption.get underTest.listRecordSetChanges(okZone.id, authPrincipal = okAuth).value.unsafeRunSync().toOption.get
val expectedResults = ListRecordSetChangesResponse( val expectedResults = ListRecordSetChangesResponse(
zoneId = okZone.id, zoneId = okZone.id,
recordSetChanges = List(), recordSetChanges = List(),
@ -2062,7 +2065,7 @@ class RecordSetServiceSpec
"return a NotAuthorizedError" in { "return a NotAuthorizedError" in {
val error = val error =
underTest.listRecordSetChanges(Some(zoneNotAuthorized.id), authPrincipal = okAuth).value.unsafeRunSync().swap.toOption.get underTest.listRecordSetChanges(zoneNotAuthorized.id, authPrincipal = okAuth).value.unsafeRunSync().swap.toOption.get
error shouldBe a[NotAuthorizedError] error shouldBe a[NotAuthorizedError]
} }
@ -2079,7 +2082,7 @@ class RecordSetServiceSpec
.getUsers(any[Set[String]], any[Option[String]], any[Option[Int]]) .getUsers(any[Set[String]], any[Option[String]], any[Option[Int]])
val result: ListRecordSetChangesResponse = val result: ListRecordSetChangesResponse =
underTest.listRecordSetChanges(Some(okZone.id), authPrincipal = okAuth).value.unsafeRunSync().toOption.get underTest.listRecordSetChanges(okZone.id, authPrincipal = okAuth).value.unsafeRunSync().toOption.get
val changesWithName = val changesWithName =
List(RecordSetChangeInfo(rsChange2, Some("ok")), RecordSetChangeInfo(rsChange1, Some("ok"))) List(RecordSetChangeInfo(rsChange2, Some("ok")), RecordSetChangeInfo(rsChange1, Some("ok")))
val expectedResults = ListRecordSetChangesResponse( val expectedResults = ListRecordSetChangesResponse(

View File

@ -758,16 +758,14 @@ class RecordSetRoutingSpec
}.toResult }.toResult
def listRecordSetChanges( def listRecordSetChanges(
zoneId: Option[String], zoneId: String,
startFrom: Option[Int], startFrom: Option[Int],
maxItems: Int, maxItems: Int,
fqdn: Option[String],
recordType: Option[RecordType],
authPrincipal: AuthPrincipal authPrincipal: AuthPrincipal
): Result[ListRecordSetChangesResponse] = { ): Result[ListRecordSetChangesResponse] = {
zoneId match { zoneId match {
case Some(zoneNotFound.id) => Left(ZoneNotFoundError(s"$zoneId")) case zoneNotFound.id => Left(ZoneNotFoundError(s"$zoneId"))
case Some(notAuthorizedZone.id) => Left(NotAuthorizedError("no way")) case notAuthorizedZone.id => Left(NotAuthorizedError("no way"))
case _ => Right(listRecordSetChangesResponse) case _ => Right(listRecordSetChangesResponse)
} }
}.toResult }.toResult

View File

@ -17,7 +17,7 @@ can read data from the Directory. Once you have that information, proceed to th
**Considerations** **Considerations**
You _should_ communicate to your Directory over LDAP using TLS. To do so, the SSL certs should be installed You _should_ communicate to your Directory over LDAP using TLS. To do so, the SSL certs should be installed
on the portal servers, or provided via a java trust store (key store). The portal provides an option to specific on the portal servers, or provided via a java trust store (key store). The portal provides an option to specific
a java key store when it starts up. a java key store when it starts up. For more information: [Using Java Key Store In VinylDNS](https://github.com/vinyldns/vinyldns/tree/master/modules/portal#building-locally)
## Configuring LDAP ## Configuring LDAP
Before you can configure LDAP, make note of the host, username, and password that you will be using. Before you can configure LDAP, make note of the host, username, and password that you will be using.

View File

@ -23,7 +23,7 @@ import vinyldns.core.domain.record.RecordType.RecordType
import vinyldns.core.domain.record._ import vinyldns.core.domain.record._
import vinyldns.core.protobuf.ProtobufConversions import vinyldns.core.protobuf.ProtobufConversions
import vinyldns.core.route.Monitored import vinyldns.core.route.Monitored
import vinyldns.mysql.repository.MySqlRecordSetRepository.fromRecordType import vinyldns.mysql.repository.MySqlRecordSetRepository.{fromRecordType, toFQDN}
import vinyldns.proto.VinylDNSProto import vinyldns.proto.VinylDNSProto
class MySqlRecordChangeRepository class MySqlRecordChangeRepository
@ -103,7 +103,7 @@ class MySqlRecordChangeRepository
change.zoneId, change.zoneId,
change.created.toEpochMilli, change.created.toEpochMilli,
fromChangeType(change.changeType), fromChangeType(change.changeType),
if(change.recordSet.name == change.zone.name) change.zone.name else change.recordSet.name + "." + change.zone.name, toFQDN(change.zone.name, change.recordSet.name),
fromRecordType(change.recordSet.typ), fromRecordType(change.recordSet.typ),
toPB(change).toByteArray, toPB(change).toByteArray,
) )

View File

@ -347,7 +347,7 @@
name="name" name="name"
class="form-control" class="form-control"
ng-model="currentGroup.name" ng-model="currentGroup.name"
ng-model-options="{ updateOn: 'submit' }" ng-change="checkForChanges()"
type="text" type="text"
required> required>
</input> </input>
@ -360,7 +360,7 @@
name="email" name="email"
class="form-control" class="form-control"
ng-model="currentGroup.email" ng-model="currentGroup.email"
ng-model-options="{ updateOn: 'submit' }" ng-change="checkForChanges()"
type="text" type="text"
required> required>
</input> </input>
@ -389,7 +389,7 @@
name="description" name="description"
class="form-control" class="form-control"
ng-model="currentGroup.description" ng-model="currentGroup.description"
ng-model-options="{ updateOn: 'submit' }" ng-change="checkForChanges()"
type="text"> type="text">
</input> </input>
<span class="help-block"> <span class="help-block">
@ -399,7 +399,7 @@
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button id="edit-group-button" class="btn btn-primary pull-right">Update</button> <button id="edit-group-button" class="btn btn-primary pull-right" ng-disabled="submitEditGroupForm.$invalid || !hasChanges">Update</button>
<button type="button" class="btn btn-default" data-dismiss="modal" ng-click="closeEditModal()">Close</button> <button type="button" class="btn btn-default" data-dismiss="modal" ng-click="closeEditModal()">Close</button>
</div> </div>
</div> </div>

View File

@ -67,6 +67,7 @@
<div class="col-md-2 pull-right"> <div class="col-md-2 pull-right">
<form class="input-group remove-bottom-margin" ng-submit="refreshRecords()"> <form class="input-group remove-bottom-margin" ng-submit="refreshRecords()">
<div class="input-group remove-bottom-margin"> <div class="input-group remove-bottom-margin">
<span class="input-group-btn"> <span class="input-group-btn">
<button id="record-search-button" type="submit" class="btn btn-primary"><span class="fa fa-search"></span></button> <button id="record-search-button" type="submit" class="btn btn-primary"><span class="fa fa-search"></span></button>
</span> </span>
@ -86,6 +87,9 @@
<div class="panel-body"> <div class="panel-body">
<div class="vinyldns-panel-top"> <div class="vinyldns-panel-top">
<div class="btn-group"> <div class="btn-group">
<span class="modal fade" id="loader" tabindex="-1" role="dialog" >
<span class="spinner" ></span>
</span>
<button id="refresh-records-button" class="btn btn-default" ng-click="refreshRecords()"><span class="fa fa-refresh"></span> Refresh</button> <button id="refresh-records-button" class="btn btn-default" ng-click="refreshRecords()"><span class="fa fa-refresh"></span> Refresh</button>
<button id="create-record-button" class="btn btn-default" ng-if="canReadZone && (zoneInfo.accessLevel == 'Delete' || canCreateRecordsViaAcl())" ng-click="createRecord(defaultTtl)"><span class="fa fa-plus"></span> Create Record Set</button> <button id="create-record-button" class="btn btn-default" ng-if="canReadZone && (zoneInfo.accessLevel == 'Delete' || canCreateRecordsViaAcl())" ng-click="createRecord(defaultTtl)"><span class="fa fa-plus"></span> Create Record Set</button>
<button id="zone-sync-button" class="btn btn-default mb-control" ng-if="zoneInfo.accessLevel=='Delete'" data-toggle="modal" data-target="#mb-sync"><span class="fa fa-exchange"></span> Sync Zone</button> <button id="zone-sync-button" class="btn btn-default mb-control" ng-if="zoneInfo.accessLevel=='Delete'" data-toggle="modal" data-target="#mb-sync"><span class="fa fa-exchange"></span> Sync Zone</button>

View File

@ -210,8 +210,8 @@ angular.module('controller.groups', []).controller('GroupsController', function
handleError(error, 'groupsService::getGroups-failure'); handleError(error, 'groupsService::getGroups-failure');
}); });
} }
//Function for fetching list of valid domains
//Function for fetching list of valid domains
$scope.validDomains=function getValidEmailDomains() { $scope.validDomains=function getValidEmailDomains() {
function success(response) { function success(response) {
$log.debug('groupsService::listEmailDomains-success', response); $log.debug('groupsService::listEmailDomains-success', response);
@ -247,10 +247,25 @@ angular.module('controller.groups', []).controller('GroupsController', function
$scope.editGroup = function (groupInfo) { $scope.editGroup = function (groupInfo) {
$scope.currentGroup = groupInfo; $scope.currentGroup = groupInfo;
$scope.initialGroup = {
name: $scope.currentGroup.name,
email: $scope.currentGroup.email,
description: $scope.currentGroup.description
};
$scope.hasChanges = false;
$scope.validDomains(); $scope.validDomains();
$("#modal_edit_group").modal("show"); $("#modal_edit_group").modal("show");
}; };
// Function to check for changes
$scope.checkForChanges = function() {
$scope.hasChanges =
$scope.currentGroup.name !== $scope.initialGroup.name ||
$scope.currentGroup.email !== $scope.initialGroup.email ||
($scope.currentGroup.description !== $scope.initialGroup.description &&
!($scope.currentGroup.description === "" && $scope.initialGroup.description === undefined));
};
$scope.getGroupAndUpdate = function(groupId, name, email, description) { $scope.getGroupAndUpdate = function(groupId, name, email, description) {
function success(response) { function success(response) {
$log.debug('groupsService::getGroup-success'); $log.debug('groupsService::getGroup-success');
@ -281,6 +296,7 @@ angular.module('controller.groups', []).controller('GroupsController', function
return groupsService.updateGroup(groupId, payload) return groupsService.updateGroup(groupId, payload)
.then(success) .then(success)
.catch(function (error) { .catch(function (error) {
$scope.closeEditModal();
handleError(error, 'groupsService::updateGroup-failure'); handleError(error, 'groupsService::updateGroup-failure');
}); });
} }

View File

@ -77,7 +77,16 @@ angular.module('service.records', [])
"recordTypeSort": recordTypeSort "recordTypeSort": recordTypeSort
}; };
var url = utilityService.urlBuilder("/api/zones/"+id+"/recordsets", params); var url = utilityService.urlBuilder("/api/zones/"+id+"/recordsets", params);
return $http.get(url); let loader = $("#loader");
loader.modal({
backdrop: "static", //remove ability to close modal with click
keyboard: false, //remove option to close with keyboard
show: true //Display loader!
})
let promis = $http.get(url);
// Hide loader when api gets response
promis.then(()=>loader.modal("hide"), ()=>loader.modal("hide"))
return promis;
}; };
this.getRecordSet = function (rsid) { this.getRecordSet = function (rsid) {

View File

@ -1,5 +1,3 @@
version: "3.8"
services: services:
# LDAP container hosting example users # LDAP container hosting example users
@ -9,7 +7,7 @@ services:
ports: ports:
- "19004:19004" - "19004:19004"
# Integration image hosting r53, sns, sqs, bind, and mysql # Integration image hosting r53, sns, sqs, bind and mysql
integration: integration:
container_name: "vinyldns-api-integration" container_name: "vinyldns-api-integration"
hostname: "vinyldns-integration" hostname: "vinyldns-integration"

View File

@ -1 +1 @@
version in ThisBuild := "0.20.0" version in ThisBuild := "0.20.1"