mirror of
https://github.com/VinylDNS/vinyldns
synced 2025-08-22 10:10:12 +00:00
Merge branch 'master' into records_ownership_transfer
This commit is contained in:
commit
d6fcd3d726
149
.github/workflows/release-beta.yml
vendored
Normal file
149
.github/workflows/release-beta.yml
vendored
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
name: VinylDNS Beta Release
|
||||||
|
concurrency:
|
||||||
|
cancel-in-progress: true
|
||||||
|
group: "release"
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
verify-first:
|
||||||
|
description: 'Verify First?'
|
||||||
|
required: true
|
||||||
|
default: 'true'
|
||||||
|
create-gh-release:
|
||||||
|
description: 'Create a GitHub Release?'
|
||||||
|
required: true
|
||||||
|
default: 'true'
|
||||||
|
publish-images:
|
||||||
|
description: 'Publish Docker Images?'
|
||||||
|
required: true
|
||||||
|
default: 'true'
|
||||||
|
pre-release:
|
||||||
|
description: 'Is this a pre-release?'
|
||||||
|
required: true
|
||||||
|
default: 'true'
|
||||||
|
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
verify:
|
||||||
|
name: Verify Release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout current branch
|
||||||
|
if: github.event.inputs.verify-first == 'true'
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Run Tests
|
||||||
|
id: verify
|
||||||
|
if: github.event.inputs.verify-first == 'true'
|
||||||
|
run: cd build/ && ./assemble_api.sh && ./run_all_tests.sh
|
||||||
|
|
||||||
|
create-gh-release:
|
||||||
|
name: Create GitHub Release
|
||||||
|
needs: verify
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event.inputs.create-gh-release == 'true'
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout current branch
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Build Artifacts
|
||||||
|
id: build
|
||||||
|
run: cd build/ && ./assemble_api.sh && ./assemble_portal.sh
|
||||||
|
|
||||||
|
- name: Get Version
|
||||||
|
id: get-version
|
||||||
|
run: echo "::set-output name=vinyldns_version::$(awk -F'"' '{print $2}' ./version.sbt)"
|
||||||
|
|
||||||
|
- name: Create GitHub Release
|
||||||
|
id: create_release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
tag_name: v${{ steps.get-version.outputs.vinyldns_version }}
|
||||||
|
generate_release_notes: true
|
||||||
|
files: artifacts/*
|
||||||
|
prerelease: ${{ github.event.inputs['pre-release'] == 'true' }}
|
||||||
|
|
||||||
|
docker-release-api:
|
||||||
|
name: Release API Docker Image
|
||||||
|
needs: [ verify, create-gh-release ]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event.inputs.publish-images == 'true'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Get Version
|
||||||
|
id: get-version
|
||||||
|
run: echo "::set-output name=vinyldns_version::$(curl -s https://api.github.com/repos/vinyldns/vinyldns/releases | jq -rc '.[0].tag_name')"
|
||||||
|
|
||||||
|
- name: Checkout current branch (full)
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ steps.get-version.outputs.vinyldns_version }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_USER }}
|
||||||
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
|
|
||||||
|
- name: Import Content Trust Key
|
||||||
|
run: docker trust key load <(echo "${SIGNING_KEY}") --name vinyldns_svc
|
||||||
|
env:
|
||||||
|
SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
|
||||||
|
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ secrets.DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE }}
|
||||||
|
|
||||||
|
# This will publish the latest release
|
||||||
|
- name: Publish API Docker Image
|
||||||
|
run: make -C build/docker/api publish
|
||||||
|
env:
|
||||||
|
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ secrets.DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE }}
|
||||||
|
|
||||||
|
docker-release-portal:
|
||||||
|
name: Release Portal Docker Image
|
||||||
|
needs: [ verify, create-gh-release ]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event.inputs.publish-images == 'true'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Get Version
|
||||||
|
id: get-version
|
||||||
|
run: echo "::set-output name=vinyldns_version::$(curl -s https://api.github.com/repos/vinyldns/vinyldns/releases | jq -rc '.[0].tag_name')"
|
||||||
|
|
||||||
|
- name: Checkout current branch (full)
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ steps.get-version.outputs.vinyldns_version }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_USER }}
|
||||||
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
|
|
||||||
|
- name: Import Content Trust Key
|
||||||
|
run: docker trust key load <(echo "${SIGNING_KEY}") --name vinyldns_svc
|
||||||
|
env:
|
||||||
|
SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
|
||||||
|
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ secrets.DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE }}
|
||||||
|
|
||||||
|
# This will publish the latest release
|
||||||
|
- name: Publish Portal Docker Image
|
||||||
|
run: make -C build/docker/portal publish
|
||||||
|
env:
|
||||||
|
DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: ${{ secrets.DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE }}
|
14
.github/workflows/release.yml
vendored
14
.github/workflows/release.yml
vendored
@ -34,7 +34,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout current branch
|
- name: Checkout current branch
|
||||||
if: github.event.inputs.verify-first == 'true'
|
if: github.event.inputs.verify-first == 'true'
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@ -53,7 +53,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout current branch
|
- name: Checkout current branch
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Create GitHub Release
|
- name: Create GitHub Release
|
||||||
id: create_release
|
id: create_release
|
||||||
uses: softprops/action-gh-release@1e07f4398721186383de40550babbdf2b84acfc5 # v0.1.14
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
tag_name: v${{ steps.get-version.outputs.vinyldns_version }}
|
tag_name: v${{ steps.get-version.outputs.vinyldns_version }}
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
@ -85,13 +85,13 @@ jobs:
|
|||||||
run: echo "::set-output name=vinyldns_version::$(curl -s https://api.github.com/repos/vinyldns/vinyldns/releases | jq -rc '.[0].tag_name')"
|
run: echo "::set-output name=vinyldns_version::$(curl -s https://api.github.com/repos/vinyldns/vinyldns/releases | jq -rc '.[0].tag_name')"
|
||||||
|
|
||||||
- name: Checkout current branch (full)
|
- name: Checkout current branch (full)
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ steps.get-version.outputs.vinyldns_version }}
|
ref: ${{ steps.get-version.outputs.vinyldns_version }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v1
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USER }}
|
username: ${{ secrets.DOCKER_USER }}
|
||||||
password: ${{ secrets.DOCKER_TOKEN }}
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
@ -120,13 +120,13 @@ jobs:
|
|||||||
run: echo "::set-output name=vinyldns_version::$(curl -s https://api.github.com/repos/vinyldns/vinyldns/releases | jq -rc '.[0].tag_name')"
|
run: echo "::set-output name=vinyldns_version::$(curl -s https://api.github.com/repos/vinyldns/vinyldns/releases | jq -rc '.[0].tag_name')"
|
||||||
|
|
||||||
- name: Checkout current branch (full)
|
- name: Checkout current branch (full)
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ steps.get-version.outputs.vinyldns_version }}
|
ref: ${{ steps.get-version.outputs.vinyldns_version }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v1
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USER }}
|
username: ${{ secrets.DOCKER_USER }}
|
||||||
password: ${{ secrets.DOCKER_TOKEN }}
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
|
8
.github/workflows/verify.yml
vendored
8
.github/workflows/verify.yml
vendored
@ -31,28 +31,28 @@ jobs:
|
|||||||
|
|
||||||
- name: Codecov
|
- name: Codecov
|
||||||
id: codecov0
|
id: codecov0
|
||||||
uses: codecov/codecov-action@v2
|
uses: codecov/codecov-action@v4
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
- name: Codecov Retry
|
- name: Codecov Retry
|
||||||
id: codecov1
|
id: codecov1
|
||||||
if: steps.codecov0.outcome=='failure'
|
if: steps.codecov0.outcome=='failure'
|
||||||
uses: codecov/codecov-action@v2
|
uses: codecov/codecov-action@v4
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
- name: Codecov Retry 2
|
- name: Codecov Retry 2
|
||||||
id: codecov2
|
id: codecov2
|
||||||
if: steps.codecov1.outcome=='failure'
|
if: steps.codecov1.outcome=='failure'
|
||||||
uses: codecov/codecov-action@v2
|
uses: codecov/codecov-action@v4
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
- name: Codecov Retry 3
|
- name: Codecov Retry 3
|
||||||
id: codecov3
|
id: codecov3
|
||||||
if: steps.codecov2.outcome=='failure'
|
if: steps.codecov2.outcome=='failure'
|
||||||
uses: codecov/codecov-action@v2
|
uses: codecov/codecov-action@v4
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
|
@ -351,6 +351,10 @@ akka.http {
|
|||||||
# Set to `infinite` to disable.
|
# Set to `infinite` to disable.
|
||||||
bind-timeout = 5s
|
bind-timeout = 5s
|
||||||
|
|
||||||
|
# A default request timeout is applied globally to all routes and can be configured using the
|
||||||
|
# akka.http.server.request-timeout setting (which defaults to 20 seconds).
|
||||||
|
# request-timeout = 60s
|
||||||
|
|
||||||
# Show verbose error messages back to the client
|
# Show verbose error messages back to the client
|
||||||
verbose-error-messages = on
|
verbose-error-messages = on
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
<logger name="scalikejdbc.StatementExecutor$$anon$1" level="OFF"/>
|
<logger name="scalikejdbc.StatementExecutor$$anon$1" level="OFF"/>
|
||||||
|
|
||||||
<logger name="com.zaxxer.hikari" level="TRACE">
|
<logger name="com.zaxxer.hikari" level="ERROR">
|
||||||
<appender-ref ref="CONSOLE"/>
|
<appender-ref ref="CONSOLE"/>
|
||||||
</logger>
|
</logger>
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
<logger name="play" level="INFO" />
|
<logger name="play" level="INFO" />
|
||||||
<logger name="application" level="DEBUG" />
|
<logger name="application" level="DEBUG" />
|
||||||
|
|
||||||
<logger name="com.zaxxer.hikari" level="TRACE">
|
<logger name="com.zaxxer.hikari" level="ERROR">
|
||||||
<appender-ref ref="CONSOLE"/>
|
<appender-ref ref="CONSOLE"/>
|
||||||
</logger>
|
</logger>
|
||||||
|
|
||||||
|
@ -348,6 +348,10 @@ akka.http {
|
|||||||
# Set to `infinite` to disable.
|
# Set to `infinite` to disable.
|
||||||
bind-timeout = 5s
|
bind-timeout = 5s
|
||||||
|
|
||||||
|
# A default request timeout is applied globally to all routes and can be configured using the
|
||||||
|
# akka.http.server.request-timeout setting (which defaults to 20 seconds).
|
||||||
|
# request-timeout = 60s
|
||||||
|
|
||||||
# Show verbose error messages back to the client
|
# Show verbose error messages back to the client
|
||||||
verbose-error-messages = on
|
verbose-error-messages = on
|
||||||
}
|
}
|
||||||
|
@ -367,6 +367,10 @@ akka.http {
|
|||||||
# Set to `infinite` to disable.
|
# Set to `infinite` to disable.
|
||||||
bind-timeout = 5s
|
bind-timeout = 5s
|
||||||
|
|
||||||
|
# A default request timeout is applied globally to all routes and can be configured using the
|
||||||
|
# akka.http.server.request-timeout setting (which defaults to 20 seconds).
|
||||||
|
# request-timeout = 60s
|
||||||
|
|
||||||
# Show verbose error messages back to the client
|
# Show verbose error messages back to the client
|
||||||
verbose-error-messages = on
|
verbose-error-messages = on
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
<logger name="com.zaxxer.hikari" level="TRACE">
|
<logger name="com.zaxxer.hikari" level="ERROR">
|
||||||
<appender-ref ref="CONSOLE"/>
|
<appender-ref ref="CONSOLE"/>
|
||||||
</logger>
|
</logger>
|
||||||
|
|
||||||
|
@ -30,7 +30,6 @@ import vinyldns.core.domain.zone.Zone
|
|||||||
import vinyldns.core.domain.batch._
|
import vinyldns.core.domain.batch._
|
||||||
import vinyldns.core.domain.record.RecordType.{RecordType, UNKNOWN}
|
import vinyldns.core.domain.record.RecordType.{RecordType, UNKNOWN}
|
||||||
import vinyldns.core.queue.MessageQueue
|
import vinyldns.core.queue.MessageQueue
|
||||||
import java.net.InetAddress
|
|
||||||
|
|
||||||
class BatchChangeConverter(batchChangeRepo: BatchChangeRepository, messageQueue: MessageQueue)
|
class BatchChangeConverter(batchChangeRepo: BatchChangeRepository, messageQueue: MessageQueue)
|
||||||
extends BatchChangeConverterAlgebra {
|
extends BatchChangeConverterAlgebra {
|
||||||
@ -52,17 +51,16 @@ class BatchChangeConverter(batchChangeRepo: BatchChangeRepository, messageQueue:
|
|||||||
s"Converting BatchChange [${batchChange.id}] with SingleChanges [${batchChange.changes.map(_.id)}]"
|
s"Converting BatchChange [${batchChange.id}] with SingleChanges [${batchChange.changes.map(_.id)}]"
|
||||||
)
|
)
|
||||||
for {
|
for {
|
||||||
updatedBatchChange <- updateBatchChange(batchChange, groupedChanges).toRightBatchResult
|
|
||||||
recordSetChanges <- createRecordSetChangesForBatch(
|
recordSetChanges <- createRecordSetChangesForBatch(
|
||||||
updatedBatchChange.changes,
|
batchChange.changes,
|
||||||
existingZones,
|
existingZones,
|
||||||
groupedChanges,
|
groupedChanges,
|
||||||
batchChange.userId,
|
batchChange.userId,
|
||||||
ownerGroupId
|
ownerGroupId
|
||||||
).toRightBatchResult
|
).toRightBatchResult
|
||||||
_ <- allChangesWereConverted(updatedBatchChange.changes, recordSetChanges)
|
_ <- allChangesWereConverted(batchChange.changes, recordSetChanges)
|
||||||
_ <- batchChangeRepo
|
_ <- batchChangeRepo
|
||||||
.save(updatedBatchChange)
|
.save(batchChange)
|
||||||
.toBatchResult // need to save the change before queueing, backend processing expects the changes to exist
|
.toBatchResult // need to save the change before queueing, backend processing expects the changes to exist
|
||||||
queued <- putChangesOnQueue(recordSetChanges, batchChange.id)
|
queued <- putChangesOnQueue(recordSetChanges, batchChange.id)
|
||||||
changeToStore = updateWithQueueingFailures(batchChange, queued)
|
changeToStore = updateWithQueueingFailures(batchChange, queued)
|
||||||
@ -129,7 +127,7 @@ class BatchChangeConverter(batchChangeRepo: BatchChangeRepository, messageQueue:
|
|||||||
change match {
|
change match {
|
||||||
case _: SingleDeleteRRSetChange if change.recordSetId.isEmpty =>
|
case _: SingleDeleteRRSetChange if change.recordSetId.isEmpty =>
|
||||||
// Mark as Complete since we don't want to throw it as an error
|
// Mark as Complete since we don't want to throw it as an error
|
||||||
change.withDoesNotExistMessage(nonExistentRecordDeleteMessage)
|
change.withDoesNotExistMessage
|
||||||
case _ =>
|
case _ =>
|
||||||
// Failure here means there was a message queue issue for this change
|
// Failure here means there was a message queue issue for this change
|
||||||
change.withFailureMessage(failedMessage)
|
change.withFailureMessage(failedMessage)
|
||||||
@ -142,35 +140,12 @@ class BatchChangeConverter(batchChangeRepo: BatchChangeRepository, messageQueue:
|
|||||||
def storeQueuingFailures(batchChange: BatchChange): BatchResult[Unit] = {
|
def storeQueuingFailures(batchChange: BatchChange): BatchResult[Unit] = {
|
||||||
// Update if Single change is Failed or if a record that does not exist is deleted
|
// Update if Single change is Failed or if a record that does not exist is deleted
|
||||||
val failedAndNotExistsChanges = batchChange.changes.collect {
|
val failedAndNotExistsChanges = batchChange.changes.collect {
|
||||||
case change if change.status == SingleChangeStatus.Failed || change.systemMessage.contains(nonExistentRecordDeleteMessage) => change
|
case change if change.status == SingleChangeStatus.Failed || change.systemMessage.contains(nonExistentRecordDeleteMessage) || change.systemMessage.contains(nonExistentRecordDataDeleteMessage) => change
|
||||||
}
|
}
|
||||||
batchChangeRepo.updateSingleChanges(failedAndNotExistsChanges).as(())
|
val storeChanges = batchChangeRepo.updateSingleChanges(failedAndNotExistsChanges).as(())
|
||||||
|
storeChanges
|
||||||
}.toBatchResult
|
}.toBatchResult
|
||||||
|
|
||||||
def matchRecordData(existingRecordSetData: List[RecordData], recordData: RecordData): Boolean =
|
|
||||||
existingRecordSetData.exists { rd =>
|
|
||||||
(rd, recordData) match {
|
|
||||||
case (AAAAData(rdAddress), AAAAData(proposedAddress)) =>
|
|
||||||
InetAddress.getByName(proposedAddress).getHostName == InetAddress
|
|
||||||
.getByName(rdAddress)
|
|
||||||
.getHostName
|
|
||||||
case _ => rd == recordData
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def updateBatchChange(batchChange: BatchChange, groupedChanges: ChangeForValidationMap): BatchChange = {
|
|
||||||
// Update system message to be display the information if record data doesn't exist for the delete request
|
|
||||||
val singleChanges = batchChange.changes.map {
|
|
||||||
case change@(sd: SingleDeleteRRSetChange) =>
|
|
||||||
if (sd.recordData.isDefined && !groupedChanges.getExistingRecordSet(change.recordKey.get).exists(rs => matchRecordData(rs.records, sd.recordData.get))) {
|
|
||||||
sd.copy(systemMessage = Some(nonExistentRecordDataDeleteMessage))
|
|
||||||
}
|
|
||||||
else change
|
|
||||||
case change => change
|
|
||||||
}
|
|
||||||
batchChange.copy(changes = singleChanges)
|
|
||||||
}
|
|
||||||
|
|
||||||
def createRecordSetChangesForBatch(
|
def createRecordSetChangesForBatch(
|
||||||
changes: List[SingleChange],
|
changes: List[SingleChange],
|
||||||
existingZones: ExistingZones,
|
existingZones: ExistingZones,
|
||||||
|
@ -44,12 +44,14 @@ object BatchChangeInput {
|
|||||||
sealed trait ChangeInput {
|
sealed trait ChangeInput {
|
||||||
val inputName: String
|
val inputName: String
|
||||||
val typ: RecordType
|
val typ: RecordType
|
||||||
|
val systemMessage: Option[String]
|
||||||
def asNewStoredChange(errors: NonEmptyList[DomainValidationError], defaultTtl: Long): SingleChange
|
def asNewStoredChange(errors: NonEmptyList[DomainValidationError], defaultTtl: Long): SingleChange
|
||||||
}
|
}
|
||||||
|
|
||||||
final case class AddChangeInput(
|
final case class AddChangeInput(
|
||||||
inputName: String,
|
inputName: String,
|
||||||
typ: RecordType,
|
typ: RecordType,
|
||||||
|
systemMessage: Option[String],
|
||||||
ttl: Option[Long],
|
ttl: Option[Long],
|
||||||
record: RecordData
|
record: RecordData
|
||||||
) extends ChangeInput {
|
) extends ChangeInput {
|
||||||
@ -68,7 +70,7 @@ final case class AddChangeInput(
|
|||||||
knownTtl,
|
knownTtl,
|
||||||
record,
|
record,
|
||||||
SingleChangeStatus.NeedsReview,
|
SingleChangeStatus.NeedsReview,
|
||||||
None,
|
systemMessage,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
errors.toList.map(SingleChangeError(_))
|
errors.toList.map(SingleChangeError(_))
|
||||||
@ -79,6 +81,7 @@ final case class AddChangeInput(
|
|||||||
final case class DeleteRRSetChangeInput(
|
final case class DeleteRRSetChangeInput(
|
||||||
inputName: String,
|
inputName: String,
|
||||||
typ: RecordType,
|
typ: RecordType,
|
||||||
|
systemMessage: Option[String],
|
||||||
record: Option[RecordData]
|
record: Option[RecordData]
|
||||||
) extends ChangeInput {
|
) extends ChangeInput {
|
||||||
def asNewStoredChange(
|
def asNewStoredChange(
|
||||||
@ -93,7 +96,7 @@ final case class DeleteRRSetChangeInput(
|
|||||||
typ,
|
typ,
|
||||||
record,
|
record,
|
||||||
SingleChangeStatus.NeedsReview,
|
SingleChangeStatus.NeedsReview,
|
||||||
None,
|
systemMessage,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
errors.toList.map(SingleChangeError(_))
|
errors.toList.map(SingleChangeError(_))
|
||||||
@ -104,6 +107,7 @@ object AddChangeInput {
|
|||||||
def apply(
|
def apply(
|
||||||
inputName: String,
|
inputName: String,
|
||||||
typ: RecordType,
|
typ: RecordType,
|
||||||
|
systemMessage: Option[String],
|
||||||
ttl: Option[Long],
|
ttl: Option[Long],
|
||||||
record: RecordData
|
record: RecordData
|
||||||
): AddChangeInput = {
|
): AddChangeInput = {
|
||||||
@ -111,28 +115,29 @@ object AddChangeInput {
|
|||||||
case PTR => inputName
|
case PTR => inputName
|
||||||
case _ => ensureTrailingDot(inputName)
|
case _ => ensureTrailingDot(inputName)
|
||||||
}
|
}
|
||||||
new AddChangeInput(transformName, typ, ttl, record)
|
new AddChangeInput(transformName, typ, systemMessage, ttl, record)
|
||||||
}
|
}
|
||||||
|
|
||||||
def apply(sc: SingleAddChange): AddChangeInput =
|
def apply(sc: SingleAddChange): AddChangeInput =
|
||||||
AddChangeInput(sc.inputName, sc.typ, Some(sc.ttl), sc.recordData)
|
AddChangeInput(sc.inputName, sc.typ, sc.systemMessage, Some(sc.ttl), sc.recordData)
|
||||||
}
|
}
|
||||||
|
|
||||||
object DeleteRRSetChangeInput {
|
object DeleteRRSetChangeInput {
|
||||||
def apply(
|
def apply(
|
||||||
inputName: String,
|
inputName: String,
|
||||||
typ: RecordType,
|
typ: RecordType,
|
||||||
|
systemMessage: Option[String],
|
||||||
record: Option[RecordData] = None
|
record: Option[RecordData] = None
|
||||||
): DeleteRRSetChangeInput = {
|
): DeleteRRSetChangeInput = {
|
||||||
val transformName = typ match {
|
val transformName = typ match {
|
||||||
case PTR => inputName
|
case PTR => inputName
|
||||||
case _ => ensureTrailingDot(inputName)
|
case _ => ensureTrailingDot(inputName)
|
||||||
}
|
}
|
||||||
new DeleteRRSetChangeInput(transformName, typ, record)
|
new DeleteRRSetChangeInput(transformName, typ, systemMessage, record)
|
||||||
}
|
}
|
||||||
|
|
||||||
def apply(sc: SingleDeleteRRSetChange): DeleteRRSetChangeInput =
|
def apply(sc: SingleDeleteRRSetChange): DeleteRRSetChangeInput =
|
||||||
DeleteRRSetChangeInput(sc.inputName, sc.typ, sc.recordData)
|
DeleteRRSetChangeInput(sc.inputName, sc.typ, sc.systemMessage, sc.recordData)
|
||||||
}
|
}
|
||||||
|
|
||||||
object ChangeInputType extends Enumeration {
|
object ChangeInputType extends Enumeration {
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package vinyldns.api.domain.batch
|
package vinyldns.api.domain.batch
|
||||||
|
|
||||||
import java.net.InetAddress
|
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.temporal.ChronoUnit
|
import java.time.temporal.ChronoUnit
|
||||||
import cats.data._
|
import cats.data._
|
||||||
@ -315,16 +314,34 @@ class BatchChangeValidations(
|
|||||||
else
|
else
|
||||||
().validNel
|
().validNel
|
||||||
|
|
||||||
def matchRecordData(existingRecordSetData: List[RecordData], recordData: RecordData): Boolean =
|
def matchRecordData(existingRecordSetData: List[RecordData], recordData: RecordData): Boolean = {
|
||||||
existingRecordSetData.exists { rd =>
|
existingRecordSetData.par.exists { rd =>
|
||||||
(rd, recordData) match {
|
rd == recordData
|
||||||
case (AAAAData(rdAddress), AAAAData(proposedAddress)) =>
|
|
||||||
InetAddress.getByName(proposedAddress).getHostName == InetAddress
|
|
||||||
.getByName(rdAddress)
|
|
||||||
.getHostName
|
|
||||||
case _ => rd == recordData
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def ensureRecordExists(
|
||||||
|
change: ChangeForValidation,
|
||||||
|
groupedChanges: ChangeForValidationMap
|
||||||
|
): Boolean = {
|
||||||
|
change match {
|
||||||
|
// For DeleteRecord inputs, need to verify that the record data actually exists
|
||||||
|
case DeleteRRSetChangeForValidation(_, _, DeleteRRSetChangeInput(_, _, _, Some(recordData)))
|
||||||
|
if !groupedChanges
|
||||||
|
.getExistingRecordSet(change.recordKey)
|
||||||
|
.exists(rs => matchRecordData(rs.records, recordData)) =>
|
||||||
|
false
|
||||||
|
case _ =>
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def updateSystemMessage(changeInput: ChangeInput, systemMessage: String): ChangeInput = {
|
||||||
|
changeInput match {
|
||||||
|
case dci: DeleteRRSetChangeInput => dci.copy(systemMessage = Some(systemMessage))
|
||||||
|
case _ => changeInput
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def validateDeleteWithContext(
|
def validateDeleteWithContext(
|
||||||
change: ChangeForValidation,
|
change: ChangeForValidation,
|
||||||
@ -333,26 +350,37 @@ class BatchChangeValidations(
|
|||||||
isApproved: Boolean
|
isApproved: Boolean
|
||||||
): SingleValidation[ChangeForValidation] = {
|
): SingleValidation[ChangeForValidation] = {
|
||||||
|
|
||||||
// To handle add and delete for the record with same record data is present in the batch
|
val nonExistentRecordDeleteMessage = "This record does not exist. No further action is required."
|
||||||
|
val nonExistentRecordDataDeleteMessage = "Record data entered does not exist. No further action is required."
|
||||||
|
|
||||||
val recordData = change match {
|
val recordData = change match {
|
||||||
case AddChangeForValidation(_, _, inputChange, _, _) => inputChange.record.toString
|
case AddChangeForValidation(_, _, inputChange, _, _) => inputChange.record.toString
|
||||||
case DeleteRRSetChangeForValidation(_, _, inputChange) => if(inputChange.record.isDefined) inputChange.record.get.toString else ""
|
case DeleteRRSetChangeForValidation(_, _, inputChange) => inputChange.record.map(_.toString).getOrElse("")
|
||||||
}
|
}
|
||||||
|
|
||||||
val addInBatch = groupedChanges.getProposedAdds(change.recordKey)
|
val addInBatch = groupedChanges.getProposedAdds(change.recordKey)
|
||||||
val isSameRecordUpdateInBatch = if(recordData.nonEmpty){
|
val isSameRecordUpdateInBatch = recordData.nonEmpty && addInBatch.contains(RecordData.fromString(recordData, change.inputChange.typ).get)
|
||||||
if(addInBatch.contains(RecordData.fromString(recordData, change.inputChange.typ).get)) true else false
|
|
||||||
} else false
|
|
||||||
|
|
||||||
val validations =
|
// Perform the system message update based on the condition
|
||||||
groupedChanges.getExistingRecordSet(change.recordKey) match {
|
val updatedChange = if (groupedChanges.getExistingRecordSet(change.recordKey).isEmpty && !isSameRecordUpdateInBatch) {
|
||||||
case Some(rs) =>
|
val updatedChangeInput = updateSystemMessage(change.inputChange, nonExistentRecordDeleteMessage)
|
||||||
userCanDeleteRecordSet(change, auth, rs.ownerGroupId, rs.records) |+|
|
change.withUpdatedInputChange(updatedChangeInput)
|
||||||
zoneDoesNotRequireManualReview(change, isApproved)
|
} else if (!ensureRecordExists(change, groupedChanges)) {
|
||||||
case None =>
|
val updatedChangeInput = updateSystemMessage(change.inputChange, nonExistentRecordDataDeleteMessage)
|
||||||
if(isSameRecordUpdateInBatch) InvalidUpdateRequest(change.inputChange.inputName).invalidNel else ().validNel
|
change.withUpdatedInputChange(updatedChangeInput)
|
||||||
}
|
} else {
|
||||||
validations.map(_ => change)
|
change
|
||||||
|
}
|
||||||
|
|
||||||
|
val validations = groupedChanges.getExistingRecordSet(updatedChange.recordKey) match {
|
||||||
|
case Some(rs) =>
|
||||||
|
userCanDeleteRecordSet(updatedChange, auth, rs.ownerGroupId, rs.records) |+|
|
||||||
|
zoneDoesNotRequireManualReview(updatedChange, isApproved)
|
||||||
|
case None =>
|
||||||
|
if (isSameRecordUpdateInBatch) InvalidUpdateRequest(updatedChange.inputChange.inputName).invalidNel else ().validNel
|
||||||
|
}
|
||||||
|
|
||||||
|
validations.map(_ => updatedChange)
|
||||||
}
|
}
|
||||||
|
|
||||||
def validateAddUpdateWithContext(
|
def validateAddUpdateWithContext(
|
||||||
@ -397,28 +425,40 @@ class BatchChangeValidations(
|
|||||||
isApproved: Boolean
|
isApproved: Boolean
|
||||||
): SingleValidation[ChangeForValidation] = {
|
): SingleValidation[ChangeForValidation] = {
|
||||||
|
|
||||||
|
val nonExistentRecordDeleteMessage = "This record does not exist. No further action is required."
|
||||||
|
val nonExistentRecordDataDeleteMessage = "Record data entered does not exist. No further action is required."
|
||||||
|
|
||||||
// To handle add and delete for the record with same record data is present in the batch
|
// To handle add and delete for the record with same record data is present in the batch
|
||||||
val recordData = change match {
|
val recordData = change match {
|
||||||
case AddChangeForValidation(_, _, inputChange, _, _) => inputChange.record.toString
|
case AddChangeForValidation(_, _, inputChange, _, _) => inputChange.record.toString
|
||||||
case DeleteRRSetChangeForValidation(_, _, inputChange) => if(inputChange.record.isDefined) inputChange.record.get.toString else ""
|
case DeleteRRSetChangeForValidation(_, _, inputChange) => inputChange.record.map(_.toString).getOrElse("")
|
||||||
}
|
}
|
||||||
|
|
||||||
val addInBatch = groupedChanges.getProposedAdds(change.recordKey)
|
val addInBatch = groupedChanges.getProposedAdds(change.recordKey)
|
||||||
val isSameRecordUpdateInBatch = if(recordData.nonEmpty){
|
val isSameRecordUpdateInBatch = recordData.nonEmpty && addInBatch.contains(RecordData.fromString(recordData, change.inputChange.typ).get)
|
||||||
if(addInBatch.contains(RecordData.fromString(recordData, change.inputChange.typ).get)) true else false
|
|
||||||
} else false
|
// Perform the system message update based on the condition
|
||||||
|
val updatedChange = if (groupedChanges.getExistingRecordSet(change.recordKey).isEmpty && !isSameRecordUpdateInBatch) {
|
||||||
|
val updatedChangeInput = updateSystemMessage(change.inputChange, nonExistentRecordDeleteMessage)
|
||||||
|
change.withUpdatedInputChange(updatedChangeInput)
|
||||||
|
} else if (!ensureRecordExists(change, groupedChanges)) {
|
||||||
|
val updatedChangeInput = updateSystemMessage(change.inputChange, nonExistentRecordDataDeleteMessage)
|
||||||
|
change.withUpdatedInputChange(updatedChangeInput)
|
||||||
|
} else {
|
||||||
|
change
|
||||||
|
}
|
||||||
|
|
||||||
val validations =
|
val validations =
|
||||||
groupedChanges.getExistingRecordSet(change.recordKey) match {
|
groupedChanges.getExistingRecordSet(updatedChange.recordKey) match {
|
||||||
case Some(rs) =>
|
case Some(rs) =>
|
||||||
val adds = groupedChanges.getProposedAdds(change.recordKey).toList
|
val adds = groupedChanges.getProposedAdds(updatedChange.recordKey).toList
|
||||||
userCanUpdateRecordSet(change, auth, rs.ownerGroupId, adds) |+|
|
userCanUpdateRecordSet(updatedChange, auth, rs.ownerGroupId, adds) |+|
|
||||||
zoneDoesNotRequireManualReview(change, isApproved)
|
zoneDoesNotRequireManualReview(updatedChange, isApproved)
|
||||||
case None =>
|
case None =>
|
||||||
if(isSameRecordUpdateInBatch) InvalidUpdateRequest(change.inputChange.inputName).invalidNel else ().validNel
|
if(isSameRecordUpdateInBatch) InvalidUpdateRequest(updatedChange.inputChange.inputName).invalidNel else ().validNel
|
||||||
}
|
}
|
||||||
|
|
||||||
validations.map(_ => change)
|
validations.map(_ => updatedChange)
|
||||||
}
|
}
|
||||||
|
|
||||||
def validateAddWithContext(
|
def validateAddWithContext(
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package vinyldns.api.domain.batch
|
package vinyldns.api.domain.batch
|
||||||
|
|
||||||
import java.net.InetAddress
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
import vinyldns.api.domain.ReverseZoneHelpers
|
import vinyldns.api.domain.ReverseZoneHelpers
|
||||||
@ -24,7 +23,7 @@ import vinyldns.api.domain.batch.BatchChangeInterfaces.ValidatedBatch
|
|||||||
import vinyldns.api.domain.batch.BatchTransformations.LogicalChangeType.LogicalChangeType
|
import vinyldns.api.domain.batch.BatchTransformations.LogicalChangeType.LogicalChangeType
|
||||||
import vinyldns.api.backend.dns.DnsConversions.getIPv6FullReverseName
|
import vinyldns.api.backend.dns.DnsConversions.getIPv6FullReverseName
|
||||||
import vinyldns.core.domain.batch._
|
import vinyldns.core.domain.batch._
|
||||||
import vinyldns.core.domain.record.{AAAAData, RecordData, RecordSet, RecordSetChange}
|
import vinyldns.core.domain.record.{RecordData, RecordSet, RecordSetChange}
|
||||||
import vinyldns.core.domain.record.RecordType._
|
import vinyldns.core.domain.record.RecordType._
|
||||||
import vinyldns.core.domain.zone.Zone
|
import vinyldns.core.domain.zone.Zone
|
||||||
import vinyldns.core.domain.record.RecordType.RecordType
|
import vinyldns.core.domain.record.RecordType.RecordType
|
||||||
@ -82,6 +81,7 @@ object BatchTransformations {
|
|||||||
val recordKey = RecordKey(zone.id, recordName, inputChange.typ)
|
val recordKey = RecordKey(zone.id, recordName, inputChange.typ)
|
||||||
def asStoredChange(changeId: Option[String] = None): SingleChange
|
def asStoredChange(changeId: Option[String] = None): SingleChange
|
||||||
def isAddChangeForValidation: Boolean
|
def isAddChangeForValidation: Boolean
|
||||||
|
def withUpdatedInputChange(inputChange: ChangeInput): ChangeForValidation
|
||||||
}
|
}
|
||||||
|
|
||||||
object ChangeForValidation {
|
object ChangeForValidation {
|
||||||
@ -118,7 +118,7 @@ object BatchTransformations {
|
|||||||
ttl,
|
ttl,
|
||||||
inputChange.record,
|
inputChange.record,
|
||||||
SingleChangeStatus.Pending,
|
SingleChangeStatus.Pending,
|
||||||
None,
|
inputChange.systemMessage,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
List.empty,
|
List.empty,
|
||||||
@ -127,6 +127,10 @@ object BatchTransformations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def isAddChangeForValidation: Boolean = true
|
def isAddChangeForValidation: Boolean = true
|
||||||
|
|
||||||
|
def withUpdatedInputChange(inputChange: ChangeInput): ChangeForValidation = {
|
||||||
|
this.copy(inputChange = inputChange.asInstanceOf[AddChangeInput])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final case class DeleteRRSetChangeForValidation(
|
final case class DeleteRRSetChangeForValidation(
|
||||||
@ -143,7 +147,7 @@ object BatchTransformations {
|
|||||||
inputChange.typ,
|
inputChange.typ,
|
||||||
inputChange.record,
|
inputChange.record,
|
||||||
SingleChangeStatus.Pending,
|
SingleChangeStatus.Pending,
|
||||||
None,
|
inputChange.systemMessage,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
List.empty,
|
List.empty,
|
||||||
@ -151,6 +155,10 @@ object BatchTransformations {
|
|||||||
)
|
)
|
||||||
|
|
||||||
def isAddChangeForValidation: Boolean = false
|
def isAddChangeForValidation: Boolean = false
|
||||||
|
|
||||||
|
def withUpdatedInputChange(inputChange: ChangeInput): ChangeForValidation = {
|
||||||
|
this.copy(inputChange = inputChange.asInstanceOf[DeleteRRSetChangeInput])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final case class BatchConversionOutput(
|
final case class BatchConversionOutput(
|
||||||
@ -197,13 +205,6 @@ object BatchTransformations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
object ValidationChanges {
|
object ValidationChanges {
|
||||||
def matchRecordData(existingRecord: RecordData, recordData: String): Boolean =
|
|
||||||
existingRecord match {
|
|
||||||
case AAAAData(address) =>
|
|
||||||
InetAddress.getByName(address).getHostName ==
|
|
||||||
InetAddress.getByName(recordData).getHostName
|
|
||||||
case _ => false
|
|
||||||
}
|
|
||||||
|
|
||||||
def apply(
|
def apply(
|
||||||
changes: List[ChangeForValidation],
|
changes: List[ChangeForValidation],
|
||||||
@ -223,16 +224,11 @@ object BatchTransformations {
|
|||||||
case DeleteRRSetChangeForValidation(
|
case DeleteRRSetChangeForValidation(
|
||||||
_,
|
_,
|
||||||
_,
|
_,
|
||||||
DeleteRRSetChangeInput(_, AAAA, Some(AAAAData(address)))
|
DeleteRRSetChangeInput(_, _, _, Some(recordData))
|
||||||
) =>
|
|
||||||
existingRecords.filter(r => matchRecordData(r, address))
|
|
||||||
case DeleteRRSetChangeForValidation(
|
|
||||||
_,
|
|
||||||
_,
|
|
||||||
DeleteRRSetChangeInput(_, _, Some(recordData))
|
|
||||||
) =>
|
) =>
|
||||||
Set(recordData)
|
Set(recordData)
|
||||||
case _: DeleteRRSetChangeForValidation => existingRecords
|
case _: DeleteRRSetChangeForValidation =>
|
||||||
|
existingRecords
|
||||||
}
|
}
|
||||||
.toSet
|
.toSet
|
||||||
.flatten
|
.flatten
|
||||||
|
@ -670,21 +670,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,
|
||||||
|
@ -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]
|
||||||
|
|
||||||
|
@ -38,6 +38,7 @@ object RecordSetChangeHandler extends TransactionProvider {
|
|||||||
private val outOfSyncFailureMessage: String = "This record set is out of sync with the DNS backend; sync this zone before attempting to update this record set."
|
private val outOfSyncFailureMessage: String = "This record set is out of sync with the DNS backend; sync this zone before attempting to update this record set."
|
||||||
private val incompatibleRecordFailureMessage: String = "Incompatible record in DNS."
|
private val incompatibleRecordFailureMessage: String = "Incompatible record in DNS."
|
||||||
private val syncZoneMessage: String = "This record set is out of sync with the DNS backend. Sync this zone before attempting to update this record set."
|
private val syncZoneMessage: String = "This record set is out of sync with the DNS backend. Sync this zone before attempting to update this record set."
|
||||||
|
private val wrongRecordDataMessage: String = "The record data entered doesn't exist. Please enter the correct record data or leave the field empty if it's a delete operation."
|
||||||
private val recordConflictMessage: String = "Conflict due to the record having the same name as an NS record in the same zone. Please create the record using the DNS service the NS record has been delegated to (ex. AWS r53), or use a different record name."
|
private val recordConflictMessage: String = "Conflict due to the record having the same name as an NS record in the same zone. Please create the record using the DNS service the NS record has been delegated to (ex. AWS r53), or use a different record name."
|
||||||
|
|
||||||
final case class Requeue(change: RecordSetChange) extends Throwable
|
final case class Requeue(change: RecordSetChange) extends Throwable
|
||||||
@ -392,12 +393,18 @@ object RecordSetChangeHandler extends TransactionProvider {
|
|||||||
case AlreadyApplied(_) => Completed(change.successful)
|
case AlreadyApplied(_) => Completed(change.successful)
|
||||||
case ReadyToApply(_) => Validated(change)
|
case ReadyToApply(_) => Validated(change)
|
||||||
case Failure(_, message) =>
|
case Failure(_, message) =>
|
||||||
if(message == outOfSyncFailureMessage || message == incompatibleRecordFailureMessage){
|
if(message == outOfSyncFailureMessage){
|
||||||
Completed(
|
Completed(
|
||||||
change.failed(
|
change.failed(
|
||||||
syncZoneMessage
|
syncZoneMessage
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
} else if (message == incompatibleRecordFailureMessage) {
|
||||||
|
Completed(
|
||||||
|
change.failed(
|
||||||
|
wrongRecordDataMessage
|
||||||
|
)
|
||||||
|
)
|
||||||
} else if (message == "referral") {
|
} else if (message == "referral") {
|
||||||
Completed(
|
Completed(
|
||||||
change.failed(
|
change.failed(
|
||||||
|
@ -88,6 +88,7 @@ trait BatchChangeJsonProtocol extends JsonValidation {
|
|||||||
(
|
(
|
||||||
(js \ "inputName").required[String]("Missing BatchChangeInput.changes.inputName"),
|
(js \ "inputName").required[String]("Missing BatchChangeInput.changes.inputName"),
|
||||||
recordType,
|
recordType,
|
||||||
|
(js \ "systemMessage").optional[String],
|
||||||
(js \ "ttl").optional[Long],
|
(js \ "ttl").optional[Long],
|
||||||
recordType.andThen(extractRecord(_, js \ "record"))
|
recordType.andThen(extractRecord(_, js \ "record"))
|
||||||
).mapN(AddChangeInput.apply)
|
).mapN(AddChangeInput.apply)
|
||||||
@ -114,6 +115,7 @@ trait BatchChangeJsonProtocol extends JsonValidation {
|
|||||||
(
|
(
|
||||||
(js \ "inputName").required[String]("Missing BatchChangeInput.changes.inputName"),
|
(js \ "inputName").required[String]("Missing BatchChangeInput.changes.inputName"),
|
||||||
recordType,
|
recordType,
|
||||||
|
(js \ "systemMessage").optional[String],
|
||||||
recordData
|
recordData
|
||||||
).mapN(DeleteRRSetChangeInput.apply)
|
).mapN(DeleteRRSetChangeInput.apply)
|
||||||
}
|
}
|
||||||
|
@ -16,13 +16,12 @@
|
|||||||
|
|
||||||
package vinyldns.api.route
|
package vinyldns.api.route
|
||||||
|
|
||||||
import akka.http.scaladsl.model.StatusCodes
|
import akka.http.scaladsl.model._
|
||||||
import akka.http.scaladsl.server.{RejectionHandler, Route, ValidationRejection}
|
import akka.http.scaladsl.server.{RejectionHandler, Route, ValidationRejection}
|
||||||
import vinyldns.api.config.LimitsConfig
|
|
||||||
import org.slf4j.{Logger, LoggerFactory}
|
import org.slf4j.{Logger, LoggerFactory}
|
||||||
import vinyldns.api.config.ManualReviewConfig
|
import vinyldns.api.config.{LimitsConfig, ManualReviewConfig}
|
||||||
import vinyldns.core.domain.batch._
|
|
||||||
import vinyldns.api.domain.batch._
|
import vinyldns.api.domain.batch._
|
||||||
|
import vinyldns.core.domain.batch._
|
||||||
|
|
||||||
class BatchChangeRoute(
|
class BatchChangeRoute(
|
||||||
batchChangeService: BatchChangeServiceAlgebra,
|
batchChangeService: BatchChangeServiceAlgebra,
|
||||||
@ -71,52 +70,52 @@ class BatchChangeRoute(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} ~
|
} ~
|
||||||
(get & monitor("Endpoint.listBatchChangeSummaries")) {
|
(get & monitor("Endpoint.listBatchChangeSummaries")) {
|
||||||
parameters(
|
parameters(
|
||||||
"userName".as[String].?,
|
"userName".as[String].?,
|
||||||
"dateTimeRangeStart".as[String].?,
|
"dateTimeRangeStart".as[String].?,
|
||||||
"dateTimeRangeEnd".as[String].?,
|
"dateTimeRangeEnd".as[String].?,
|
||||||
"startFrom".as[Int].?,
|
"startFrom".as[Int].?,
|
||||||
"maxItems".as[Int].?(MAX_ITEMS_LIMIT),
|
"maxItems".as[Int].?(MAX_ITEMS_LIMIT),
|
||||||
"ignoreAccess".as[Boolean].?(false),
|
"ignoreAccess".as[Boolean].?(false),
|
||||||
"approvalStatus".as[String].?
|
"approvalStatus".as[String].?
|
||||||
) {
|
) {
|
||||||
(
|
(
|
||||||
userName: Option[String],
|
userName: Option[String],
|
||||||
dateTimeRangeStart: Option[String],
|
dateTimeRangeStart: Option[String],
|
||||||
dateTimeRangeEnd: Option[String],
|
dateTimeRangeEnd: Option[String],
|
||||||
startFrom: Option[Int],
|
startFrom: Option[Int],
|
||||||
maxItems: Int,
|
maxItems: Int,
|
||||||
ignoreAccess: Boolean,
|
ignoreAccess: Boolean,
|
||||||
approvalStatus: Option[String]
|
approvalStatus: Option[String]
|
||||||
) =>
|
) =>
|
||||||
{
|
{
|
||||||
val convertApprovalStatus = approvalStatus.flatMap(BatchChangeApprovalStatus.find)
|
val convertApprovalStatus = approvalStatus.flatMap(BatchChangeApprovalStatus.find)
|
||||||
|
|
||||||
handleRejections(invalidQueryHandler) {
|
handleRejections(invalidQueryHandler) {
|
||||||
validate(
|
validate(
|
||||||
0 < maxItems && maxItems <= MAX_ITEMS_LIMIT,
|
0 < maxItems && maxItems <= MAX_ITEMS_LIMIT,
|
||||||
s"maxItems was $maxItems, maxItems must be between 1 and $MAX_ITEMS_LIMIT, inclusive."
|
s"maxItems was $maxItems, maxItems must be between 1 and $MAX_ITEMS_LIMIT, inclusive."
|
||||||
) {
|
) {
|
||||||
authenticateAndExecute(
|
authenticateAndExecute(
|
||||||
batchChangeService.listBatchChangeSummaries(
|
batchChangeService.listBatchChangeSummaries(
|
||||||
_,
|
_,
|
||||||
userName,
|
userName,
|
||||||
dateTimeRangeStart,
|
dateTimeRangeStart,
|
||||||
dateTimeRangeEnd,
|
dateTimeRangeEnd,
|
||||||
startFrom,
|
startFrom,
|
||||||
maxItems,
|
maxItems,
|
||||||
ignoreAccess,
|
ignoreAccess,
|
||||||
convertApprovalStatus
|
convertApprovalStatus
|
||||||
)
|
)
|
||||||
) { summaries =>
|
) { summaries =>
|
||||||
complete(StatusCodes.OK, summaries)
|
complete(StatusCodes.OK, summaries)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} ~
|
} ~
|
||||||
path("zones" / "batchrecordchanges" / Segment) { id =>
|
path("zones" / "batchrecordchanges" / Segment) { id =>
|
||||||
(get & monitor("Endpoint.getBatchChange")) {
|
(get & monitor("Endpoint.getBatchChange")) {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -39,8 +39,6 @@ import vinyldns.core.domain.zone.Zone
|
|||||||
class BatchChangeConverterSpec extends AnyWordSpec with Matchers {
|
class BatchChangeConverterSpec extends AnyWordSpec with Matchers {
|
||||||
private val nonExistentRecordDeleteMessage: String = "This record does not exist. " +
|
private val nonExistentRecordDeleteMessage: String = "This record does not exist. " +
|
||||||
"No further action is required."
|
"No further action is required."
|
||||||
private val nonExistentRecordDataDeleteMessage: String = "Record data entered does not exist. " +
|
|
||||||
"No further action is required."
|
|
||||||
|
|
||||||
private def makeSingleAddChange(
|
private def makeSingleAddChange(
|
||||||
name: String,
|
name: String,
|
||||||
@ -64,7 +62,7 @@ class BatchChangeConverterSpec extends AnyWordSpec with Matchers {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private def makeSingleDeleteRRSetChange(name: String, typ: RecordType, zone: Zone = okZone) = {
|
private def makeSingleDeleteRRSetChange(name: String, typ: RecordType, zone: Zone = okZone, systemMessage: Option[String] = None) = {
|
||||||
val fqdn = s"$name.${zone.name}"
|
val fqdn = s"$name.${zone.name}"
|
||||||
SingleDeleteRRSetChange(
|
SingleDeleteRRSetChange(
|
||||||
Some(zone.id),
|
Some(zone.id),
|
||||||
@ -74,7 +72,7 @@ class BatchChangeConverterSpec extends AnyWordSpec with Matchers {
|
|||||||
typ,
|
typ,
|
||||||
None,
|
None,
|
||||||
SingleChangeStatus.Pending,
|
SingleChangeStatus.Pending,
|
||||||
None,
|
systemMessage,
|
||||||
None,
|
None,
|
||||||
None
|
None
|
||||||
)
|
)
|
||||||
@ -88,18 +86,19 @@ class BatchChangeConverterSpec extends AnyWordSpec with Matchers {
|
|||||||
AddChangeForValidation(
|
AddChangeForValidation(
|
||||||
okZone,
|
okZone,
|
||||||
s"$recordName",
|
s"$recordName",
|
||||||
AddChangeInput(s"$recordName.ok.", typ, Some(123), recordData),
|
AddChangeInput(s"$recordName.ok.", typ, None, Some(123), recordData),
|
||||||
7200L
|
7200L
|
||||||
)
|
)
|
||||||
|
|
||||||
private def makeDeleteRRSetChangeForValidation(
|
private def makeDeleteRRSetChangeForValidation(
|
||||||
recordName: String,
|
recordName: String,
|
||||||
typ: RecordType = RecordType.A
|
typ: RecordType = RecordType.A,
|
||||||
|
systemMessage: Option[String] = None
|
||||||
): DeleteRRSetChangeForValidation =
|
): DeleteRRSetChangeForValidation =
|
||||||
DeleteRRSetChangeForValidation(
|
DeleteRRSetChangeForValidation(
|
||||||
okZone,
|
okZone,
|
||||||
s"$recordName",
|
s"$recordName",
|
||||||
DeleteRRSetChangeInput(s"$recordName.ok.", typ)
|
DeleteRRSetChangeInput(s"$recordName.ok.", typ, systemMessage, None)
|
||||||
)
|
)
|
||||||
|
|
||||||
private val addSingleChangesGood = List(
|
private val addSingleChangesGood = List(
|
||||||
@ -165,19 +164,11 @@ class BatchChangeConverterSpec extends AnyWordSpec with Matchers {
|
|||||||
)
|
)
|
||||||
|
|
||||||
private val singleChangesOneDelete = List(
|
private val singleChangesOneDelete = List(
|
||||||
makeSingleDeleteRRSetChange("DoesNotExistToDelete", A)
|
makeSingleDeleteRRSetChange("DoesNotExistToDelete", A, okZone, Some(nonExistentRecordDeleteMessage))
|
||||||
)
|
|
||||||
|
|
||||||
private val singleChangesOneDeleteGood = List(
|
|
||||||
makeSingleDeleteRRSetChange("aToDelete", A).copy(recordData = Some(AData("2.3.4.6"))),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
private val changeForValidationOneDelete = List(
|
private val changeForValidationOneDelete = List(
|
||||||
makeDeleteRRSetChangeForValidation("DoesNotExistToDelete", A)
|
makeDeleteRRSetChangeForValidation("DoesNotExistToDelete", A, Some(nonExistentRecordDeleteMessage))
|
||||||
)
|
|
||||||
|
|
||||||
private val changeForValidationOneDeleteGood = List(
|
|
||||||
makeDeleteRRSetChangeForValidation("aToDelete", A)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
private val singleChangesOneBad = List(
|
private val singleChangesOneBad = List(
|
||||||
@ -621,45 +612,6 @@ class BatchChangeConverterSpec extends AnyWordSpec with Matchers {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"updateBatchChange" should {
|
|
||||||
"update the batch change system message when there is a delete request with non-existent record data" in {
|
|
||||||
val batchWithBadChange =
|
|
||||||
BatchChange(
|
|
||||||
okUser.id,
|
|
||||||
okUser.userName,
|
|
||||||
None,
|
|
||||||
Instant.now.truncatedTo(ChronoUnit.MILLIS),
|
|
||||||
singleChangesOneDeleteGood,
|
|
||||||
approvalStatus = BatchChangeApprovalStatus.AutoApproved
|
|
||||||
)
|
|
||||||
val result =
|
|
||||||
underTest
|
|
||||||
.updateBatchChange(
|
|
||||||
batchWithBadChange,
|
|
||||||
ChangeForValidationMap(changeForValidationOneDeleteGood.map(_.validNel), existingRecordSets),
|
|
||||||
)
|
|
||||||
|
|
||||||
// validate the batch change returned
|
|
||||||
val receivedChange = result.changes(0)
|
|
||||||
receivedChange.systemMessage shouldBe Some(nonExistentRecordDataDeleteMessage)
|
|
||||||
result.changes(0) shouldBe singleChangesOneDeleteGood(0).copy(systemMessage = Some(nonExistentRecordDataDeleteMessage))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"matchRecordData" should {
|
|
||||||
"check if the record data given matches the record data present" in {
|
|
||||||
val recordData = List(AData("1.2.3.5"), AAAAData("caec:cec6:c4ef:bb7b:1a78:d055:216d:3a78"))
|
|
||||||
val result1 = underTest.matchRecordData(recordData, AData("1.2.3.5"))
|
|
||||||
result1 shouldBe true
|
|
||||||
val result2 = underTest.matchRecordData(recordData, AData("1.2.3.4"))
|
|
||||||
result2 shouldBe false
|
|
||||||
val result3 = underTest.matchRecordData(recordData, AAAAData("caec:cec6:c4ef:bb7b:1a78:d055:216d:3a78"))
|
|
||||||
result3 shouldBe true
|
|
||||||
val result4 = underTest.matchRecordData(recordData, AAAAData("abcd:cec6:c4ef:bb7b:1a78:d055:216d:3a78"))
|
|
||||||
result4 shouldBe false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"generateAddChange" should {
|
"generateAddChange" should {
|
||||||
val singleAddChange = makeSingleAddChange("shared-rs", AData("1.2.3.4"), A, sharedZone)
|
val singleAddChange = makeSingleAddChange("shared-rs", AData("1.2.3.4"), A, sharedZone)
|
||||||
val ownerGroupId = Some("some-owner-group-id")
|
val ownerGroupId = Some("some-owner-group-id")
|
||||||
|
@ -31,16 +31,16 @@ class BatchChangeInputSpec extends AnyWordSpec with Matchers {
|
|||||||
|
|
||||||
"BatchChangeInput" should {
|
"BatchChangeInput" should {
|
||||||
"ensure trailing dot on A, AAAA, and CNAME fqdn" in {
|
"ensure trailing dot on A, AAAA, and CNAME fqdn" in {
|
||||||
val changeA = AddChangeInput("apex.test.com", A, Some(100), AData("1.1.1.1"))
|
val changeA = AddChangeInput("apex.test.com", A, None, Some(100), AData("1.1.1.1"))
|
||||||
val changeAAAA =
|
val changeAAAA =
|
||||||
AddChangeInput("aaaa.test.com", AAAA, Some(3600), AAAAData("1:2:3:4:5:6:7:8"))
|
AddChangeInput("aaaa.test.com", AAAA, None, Some(3600), AAAAData("1:2:3:4:5:6:7:8"))
|
||||||
val changeCname =
|
val changeCname =
|
||||||
AddChangeInput("cname.test.com", CNAME, Some(100), CNAMEData(Fqdn("testing.test.com")))
|
AddChangeInput("cname.test.com", CNAME, None, Some(100), CNAMEData(Fqdn("testing.test.com")))
|
||||||
val changeADotted = AddChangeInput("adot.test.com.", A, Some(100), AData("1.1.1.1"))
|
val changeADotted = AddChangeInput("adot.test.com.", A, None, Some(100), AData("1.1.1.1"))
|
||||||
val changeAAAADotted =
|
val changeAAAADotted =
|
||||||
AddChangeInput("aaaadot.test.com.", AAAA, Some(3600), AAAAData("1:2:3:4:5:6:7:8"))
|
AddChangeInput("aaaadot.test.com.", AAAA, None, Some(3600), AAAAData("1:2:3:4:5:6:7:8"))
|
||||||
val changeCnameDotted =
|
val changeCnameDotted =
|
||||||
AddChangeInput("cnamedot.test.com.", CNAME, Some(100), CNAMEData(Fqdn("testing.test.com.")))
|
AddChangeInput("cnamedot.test.com.", CNAME, None, Some(100), CNAMEData(Fqdn("testing.test.com.")))
|
||||||
|
|
||||||
val input = BatchChangeInput(
|
val input = BatchChangeInput(
|
||||||
None,
|
None,
|
||||||
@ -58,7 +58,7 @@ class BatchChangeInputSpec extends AnyWordSpec with Matchers {
|
|||||||
}
|
}
|
||||||
"asNewStoredChange" should {
|
"asNewStoredChange" should {
|
||||||
"Convert an AddChangeInput into SingleAddChange" in {
|
"Convert an AddChangeInput into SingleAddChange" in {
|
||||||
val changeA = AddChangeInput("some.test.com", A, None, AData("1.1.1.1"))
|
val changeA = AddChangeInput("some.test.com", A, None, None, AData("1.1.1.1"))
|
||||||
val converted = changeA.asNewStoredChange(
|
val converted = changeA.asNewStoredChange(
|
||||||
NonEmptyList.of(ZoneDiscoveryError("test")),
|
NonEmptyList.of(ZoneDiscoveryError("test")),
|
||||||
VinylDNSTestHelpers.defaultTtl
|
VinylDNSTestHelpers.defaultTtl
|
||||||
@ -80,7 +80,7 @@ class BatchChangeInputSpec extends AnyWordSpec with Matchers {
|
|||||||
asAdd.recordSetId shouldBe None
|
asAdd.recordSetId shouldBe None
|
||||||
}
|
}
|
||||||
"Convert a DeleteChangeInput into SingleDeleteRRSetChange" in {
|
"Convert a DeleteChangeInput into SingleDeleteRRSetChange" in {
|
||||||
val changeA = DeleteRRSetChangeInput("some.test.com", A)
|
val changeA = DeleteRRSetChangeInput("some.test.com", A, None)
|
||||||
val converted = changeA.asNewStoredChange(
|
val converted = changeA.asNewStoredChange(
|
||||||
NonEmptyList.of(ZoneDiscoveryError("test")),
|
NonEmptyList.of(ZoneDiscoveryError("test")),
|
||||||
VinylDNSTestHelpers.defaultTtl
|
VinylDNSTestHelpers.defaultTtl
|
||||||
@ -111,14 +111,14 @@ class BatchChangeInputSpec extends AnyWordSpec with Matchers {
|
|||||||
1234,
|
1234,
|
||||||
AData("1.2.3.4"),
|
AData("1.2.3.4"),
|
||||||
SingleChangeStatus.NeedsReview,
|
SingleChangeStatus.NeedsReview,
|
||||||
Some("msg"),
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
List(SingleChangeError(DomainValidationErrorType.ZoneDiscoveryError, "test err"))
|
List(SingleChangeError(DomainValidationErrorType.ZoneDiscoveryError, "test err"))
|
||||||
)
|
)
|
||||||
|
|
||||||
val expectedAddChange =
|
val expectedAddChange =
|
||||||
AddChangeInput("testRname.testZoneName.", A, Some(1234), AData("1.2.3.4"))
|
AddChangeInput("testRname.testZoneName.", A, None, Some(1234), AData("1.2.3.4"))
|
||||||
|
|
||||||
val singleDelChange = SingleDeleteRRSetChange(
|
val singleDelChange = SingleDeleteRRSetChange(
|
||||||
Some("testZoneId"),
|
Some("testZoneId"),
|
||||||
@ -128,14 +128,14 @@ class BatchChangeInputSpec extends AnyWordSpec with Matchers {
|
|||||||
A,
|
A,
|
||||||
None,
|
None,
|
||||||
SingleChangeStatus.NeedsReview,
|
SingleChangeStatus.NeedsReview,
|
||||||
Some("msg"),
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
List(SingleChangeError(DomainValidationErrorType.ZoneDiscoveryError, "test err"))
|
List(SingleChangeError(DomainValidationErrorType.ZoneDiscoveryError, "test err"))
|
||||||
)
|
)
|
||||||
|
|
||||||
val expectedDelChange =
|
val expectedDelChange =
|
||||||
DeleteRRSetChangeInput("testRname.testZoneName.", A)
|
DeleteRRSetChangeInput("testRname.testZoneName.", A, None)
|
||||||
|
|
||||||
val change = BatchChange(
|
val change = BatchChange(
|
||||||
"userId",
|
"userId",
|
||||||
|
@ -81,34 +81,36 @@ class BatchChangeServiceSpec
|
|||||||
)
|
)
|
||||||
private val ttl = Some(200L)
|
private val ttl = Some(200L)
|
||||||
|
|
||||||
private val apexAddA = AddChangeInput("apex.test.com", RecordType.A, ttl, AData("1.1.1.1"))
|
private val apexAddA = AddChangeInput("apex.test.com", RecordType.A, None, ttl, AData("1.1.1.1"))
|
||||||
private val nonApexAddA =
|
private val nonApexAddA =
|
||||||
AddChangeInput("non-apex.test.com", RecordType.A, ttl, AData("1.1.1.1"))
|
AddChangeInput("non-apex.test.com", RecordType.A, None, ttl, AData("1.1.1.1"))
|
||||||
private val onlyApexAddA =
|
private val onlyApexAddA =
|
||||||
AddChangeInput("only.apex.exists", RecordType.A, ttl, AData("1.1.1.1"))
|
AddChangeInput("only.apex.exists", RecordType.A, None, ttl, AData("1.1.1.1"))
|
||||||
private val onlyBaseAddAAAA =
|
private val onlyBaseAddAAAA =
|
||||||
AddChangeInput("have.only.base", RecordType.AAAA, ttl, AAAAData("1:2:3:4:5:6:7:8"))
|
AddChangeInput("have.only.base", RecordType.AAAA, None, ttl, AAAAData("1:2:3:4:5:6:7:8"))
|
||||||
private val noZoneAddA = AddChangeInput("no.zone.match.", RecordType.A, ttl, AData("1.1.1.1"))
|
private val noZoneAddA = AddChangeInput("no.zone.match.", RecordType.A, None, ttl, AData("1.1.1.1"))
|
||||||
private val dottedAddA =
|
private val dottedAddA =
|
||||||
AddChangeInput("dot.ted.apex.test.com", RecordType.A, ttl, AData("1.1.1.1"))
|
AddChangeInput("dot.ted.apex.test.com", RecordType.A, None, ttl, AData("1.1.1.1"))
|
||||||
private val cnameAdd =
|
private val cnameAdd =
|
||||||
AddChangeInput("cname.test.com", RecordType.CNAME, ttl, CNAMEData(Fqdn("testing.test.com.")))
|
AddChangeInput("cname.test.com", RecordType.CNAME, None, ttl, CNAMEData(Fqdn("testing.test.com.")))
|
||||||
private val cnameApexAdd =
|
private val cnameApexAdd =
|
||||||
AddChangeInput("apex.test.com", RecordType.CNAME, ttl, CNAMEData(Fqdn("testing.test.com.")))
|
AddChangeInput("apex.test.com", RecordType.CNAME, None, ttl, CNAMEData(Fqdn("testing.test.com.")))
|
||||||
private val cnameReverseAdd = AddChangeInput(
|
private val cnameReverseAdd = AddChangeInput(
|
||||||
"cname.55.144.10.in-addr.arpa",
|
"cname.55.144.10.in-addr.arpa",
|
||||||
RecordType.CNAME,
|
RecordType.CNAME,
|
||||||
|
None,
|
||||||
ttl,
|
ttl,
|
||||||
CNAMEData(Fqdn("testing.cname.com."))
|
CNAMEData(Fqdn("testing.cname.com."))
|
||||||
)
|
)
|
||||||
private val ptrAdd = AddChangeInput("10.144.55.11", RecordType.PTR, ttl, PTRData(Fqdn("ptr")))
|
private val ptrAdd = AddChangeInput("10.144.55.11", RecordType.PTR, None, ttl, PTRData(Fqdn("ptr")))
|
||||||
private val ptrAdd2 = AddChangeInput("10.144.55.255", RecordType.PTR, ttl, PTRData(Fqdn("ptr")))
|
private val ptrAdd2 = AddChangeInput("10.144.55.255", RecordType.PTR, None, ttl, PTRData(Fqdn("ptr")))
|
||||||
private val ptrDelegatedAdd =
|
private val ptrDelegatedAdd =
|
||||||
AddChangeInput("192.0.2.193", RecordType.PTR, ttl, PTRData(Fqdn("ptr")))
|
AddChangeInput("192.0.2.193", RecordType.PTR, None, ttl, PTRData(Fqdn("ptr")))
|
||||||
private val ptrV6Add =
|
private val ptrV6Add =
|
||||||
AddChangeInput(
|
AddChangeInput(
|
||||||
"2001:0000:0000:0000:0000:ff00:0042:8329",
|
"2001:0000:0000:0000:0000:ff00:0042:8329",
|
||||||
RecordType.PTR,
|
RecordType.PTR,
|
||||||
|
None,
|
||||||
ttl,
|
ttl,
|
||||||
PTRData(Fqdn("ptr"))
|
PTRData(Fqdn("ptr"))
|
||||||
)
|
)
|
||||||
@ -489,6 +491,7 @@ class BatchChangeServiceSpec
|
|||||||
val ptr = AddChangeInput(
|
val ptr = AddChangeInput(
|
||||||
"2001:0000:0000:0001:0000:ff00:0042:8329",
|
"2001:0000:0000:0001:0000:ff00:0042:8329",
|
||||||
RecordType.PTR,
|
RecordType.PTR,
|
||||||
|
None,
|
||||||
ttl,
|
ttl,
|
||||||
PTRData(Fqdn("ptr"))
|
PTRData(Fqdn("ptr"))
|
||||||
)
|
)
|
||||||
@ -520,6 +523,7 @@ class BatchChangeServiceSpec
|
|||||||
val ptr = AddChangeInput(
|
val ptr = AddChangeInput(
|
||||||
"2001:0000:0000:0001:0000:ff00:0042:8329",
|
"2001:0000:0000:0001:0000:ff00:0042:8329",
|
||||||
RecordType.PTR,
|
RecordType.PTR,
|
||||||
|
None,
|
||||||
ttl,
|
ttl,
|
||||||
PTRData(Fqdn("ptr"))
|
PTRData(Fqdn("ptr"))
|
||||||
)
|
)
|
||||||
@ -577,12 +581,12 @@ class BatchChangeServiceSpec
|
|||||||
}
|
}
|
||||||
|
|
||||||
"succeed with excluded TTL" in {
|
"succeed with excluded TTL" in {
|
||||||
val noTtl = AddChangeInput("no-ttl-add.test.com", RecordType.A, None, AData("1.1.1.1"))
|
val noTtl = AddChangeInput("no-ttl-add.test.com", RecordType.A, None, None, AData("1.1.1.1"))
|
||||||
val withTtl =
|
val withTtl =
|
||||||
AddChangeInput("with-ttl-add-2.test.com", RecordType.A, Some(900), AData("1.1.1.1"))
|
AddChangeInput("with-ttl-add-2.test.com", RecordType.A, None, Some(900), AData("1.1.1.1"))
|
||||||
val noTtlDel = DeleteRRSetChangeInput("non-apex.test.com.", RecordType.TXT)
|
val noTtlDel = DeleteRRSetChangeInput("non-apex.test.com.", RecordType.TXT, None)
|
||||||
val noTtlUpdate =
|
val noTtlUpdate =
|
||||||
AddChangeInput("non-apex.test.com.", RecordType.TXT, None, TXTData("hello"))
|
AddChangeInput("non-apex.test.com.", RecordType.TXT, None, None, TXTData("hello"))
|
||||||
|
|
||||||
val input = BatchChangeInput(None, List(noTtl, withTtl, noTtlDel, noTtlUpdate))
|
val input = BatchChangeInput(None, List(noTtl, withTtl, noTtlDel, noTtlUpdate))
|
||||||
val result = underTest.applyBatchChange(input, auth, true).value.unsafeRunSync().toOption.get
|
val result = underTest.applyBatchChange(input, auth, true).value.unsafeRunSync().toOption.get
|
||||||
@ -1172,7 +1176,7 @@ class BatchChangeServiceSpec
|
|||||||
"0.1.0.0.2.ip6.arpa."
|
"0.1.0.0.2.ip6.arpa."
|
||||||
)
|
)
|
||||||
|
|
||||||
val ptr = AddChangeInput(ip, RecordType.PTR, ttl, PTRData(Fqdn("ptr."))).validNel
|
val ptr = AddChangeInput(ip, RecordType.PTR, None, ttl, PTRData(Fqdn("ptr."))).validNel
|
||||||
val underTestPTRZonesList: ExistingZones = underTest.getZonesForRequest(List(ptr)).unsafeRunSync()
|
val underTestPTRZonesList: ExistingZones = underTest.getZonesForRequest(List(ptr)).unsafeRunSync()
|
||||||
|
|
||||||
val zoneNames = underTestPTRZonesList.zones.map(_.name)
|
val zoneNames = underTestPTRZonesList.zones.map(_.name)
|
||||||
@ -1198,7 +1202,7 @@ class BatchChangeServiceSpec
|
|||||||
)
|
)
|
||||||
|
|
||||||
val ip = "2001:0db8:0000:0000:0000:ff00:0042:8329"
|
val ip = "2001:0db8:0000:0000:0000:ff00:0042:8329"
|
||||||
val ptr = AddChangeInput(ip, RecordType.PTR, ttl, PTRData(Fqdn("ptr."))).validNel
|
val ptr = AddChangeInput(ip, RecordType.PTR, None, ttl, PTRData(Fqdn("ptr."))).validNel
|
||||||
val underTestPTRZonesList: ExistingZones = underTest.getZonesForRequest(List(ptr)).unsafeRunSync()
|
val underTestPTRZonesList: ExistingZones = underTest.getZonesForRequest(List(ptr)).unsafeRunSync()
|
||||||
|
|
||||||
val zoneNames = underTestPTRZonesList.zones.map(_.name)
|
val zoneNames = underTestPTRZonesList.zones.map(_.name)
|
||||||
@ -1245,7 +1249,7 @@ class BatchChangeServiceSpec
|
|||||||
|
|
||||||
val ips = ip1 :: ip2s
|
val ips = ip1 :: ip2s
|
||||||
val ptrs = ips.map { v6Name =>
|
val ptrs = ips.map { v6Name =>
|
||||||
AddChangeInput(v6Name, RecordType.PTR, ttl, PTRData(Fqdn("ptr."))).validNel
|
AddChangeInput(v6Name, RecordType.PTR, None, ttl, PTRData(Fqdn("ptr."))).validNel
|
||||||
}
|
}
|
||||||
|
|
||||||
val underTestPTRZonesList: ExistingZones = underTest.getZonesForRequest(ptrs).unsafeRunSync()
|
val underTestPTRZonesList: ExistingZones = underTest.getZonesForRequest(ptrs).unsafeRunSync()
|
||||||
@ -1300,10 +1304,10 @@ class BatchChangeServiceSpec
|
|||||||
"properly discover records in forward zones" in {
|
"properly discover records in forward zones" in {
|
||||||
val apex = apexZone.name
|
val apex = apexZone.name
|
||||||
|
|
||||||
val aApex = AddChangeInput(apex, RecordType.A, ttl, AData("1.2.3.4"))
|
val aApex = AddChangeInput(apex, RecordType.A, None, ttl, AData("1.2.3.4"))
|
||||||
val aNormal = AddChangeInput(s"record.$apex", RecordType.A, ttl, AData("1.2.3.4"))
|
val aNormal = AddChangeInput(s"record.$apex", RecordType.A, None, ttl, AData("1.2.3.4"))
|
||||||
val aDotted =
|
val aDotted =
|
||||||
AddChangeInput(s"some.dotted.record.$apex", RecordType.A, ttl, AData("1.2.3.4"))
|
AddChangeInput(s"some.dotted.record.$apex", RecordType.A, None, ttl, AData("1.2.3.4"))
|
||||||
|
|
||||||
val expected = List(
|
val expected = List(
|
||||||
AddChangeForValidation(apexZone, apex, aApex, 7200L),
|
AddChangeForValidation(apexZone, apex, aApex, 7200L),
|
||||||
@ -1322,10 +1326,10 @@ class BatchChangeServiceSpec
|
|||||||
"properly discover TXT records" in {
|
"properly discover TXT records" in {
|
||||||
val apex = apexZone.name
|
val apex = apexZone.name
|
||||||
|
|
||||||
val txtApex = AddChangeInput(apex, RecordType.TXT, ttl, TXTData("test"))
|
val txtApex = AddChangeInput(apex, RecordType.TXT, None, ttl, TXTData("test"))
|
||||||
val txtNormal = AddChangeInput(s"record.$apex", RecordType.TXT, ttl, TXTData("test"))
|
val txtNormal = AddChangeInput(s"record.$apex", RecordType.TXT, None, ttl, TXTData("test"))
|
||||||
val txtDotted =
|
val txtDotted =
|
||||||
AddChangeInput(s"some.dotted.record.$apex", RecordType.TXT, ttl, TXTData("test"))
|
AddChangeInput(s"some.dotted.record.$apex", RecordType.TXT, None, ttl, TXTData("test"))
|
||||||
|
|
||||||
val expected = List(
|
val expected = List(
|
||||||
AddChangeForValidation(apexZone, apex, txtApex, 7200L),
|
AddChangeForValidation(apexZone, apex, txtApex, 7200L),
|
||||||
@ -1419,20 +1423,22 @@ class BatchChangeServiceSpec
|
|||||||
val ptrv6ZoneBig = Zone("0.1.0.0.2.ip6.arpa.", "email", id = "ptrv6big")
|
val ptrv6ZoneBig = Zone("0.1.0.0.2.ip6.arpa.", "email", id = "ptrv6big")
|
||||||
|
|
||||||
val smallZoneAdd =
|
val smallZoneAdd =
|
||||||
AddChangeInput("2001:db8::ff00:42:8329", RecordType.PTR, ttl, PTRData(Fqdn("ptr")))
|
AddChangeInput("2001:db8::ff00:42:8329", RecordType.PTR, None, ttl, PTRData(Fqdn("ptr")))
|
||||||
val medZoneAdd = AddChangeInput(
|
val medZoneAdd = AddChangeInput(
|
||||||
"2001:0db8:0111:0000:0000:ff00:0042:8329",
|
"2001:0db8:0111:0000:0000:ff00:0042:8329",
|
||||||
RecordType.PTR,
|
RecordType.PTR,
|
||||||
|
None,
|
||||||
ttl,
|
ttl,
|
||||||
PTRData(Fqdn("ptr"))
|
PTRData(Fqdn("ptr"))
|
||||||
)
|
)
|
||||||
val bigZoneAdd = AddChangeInput(
|
val bigZoneAdd = AddChangeInput(
|
||||||
"2001:0000:0000:0000:0000:ff00:0042:8329",
|
"2001:0000:0000:0000:0000:ff00:0042:8329",
|
||||||
RecordType.PTR,
|
RecordType.PTR,
|
||||||
|
None,
|
||||||
ttl,
|
ttl,
|
||||||
PTRData(Fqdn("ptr"))
|
PTRData(Fqdn("ptr"))
|
||||||
)
|
)
|
||||||
val notFoundZoneAdd = AddChangeInput("::1", RecordType.PTR, ttl, PTRData(Fqdn("ptr")))
|
val notFoundZoneAdd = AddChangeInput("::1", RecordType.PTR, None, ttl, PTRData(Fqdn("ptr")))
|
||||||
|
|
||||||
val ptripv6Adds = List(
|
val ptripv6Adds = List(
|
||||||
smallZoneAdd.validNel,
|
smallZoneAdd.validNel,
|
||||||
@ -1678,7 +1684,7 @@ class BatchChangeServiceSpec
|
|||||||
|
|
||||||
"return a BatchChange if all data inputs are valid/soft failures and manual review is enabled and owner group ID " +
|
"return a BatchChange if all data inputs are valid/soft failures and manual review is enabled and owner group ID " +
|
||||||
"is provided" in {
|
"is provided" in {
|
||||||
val delete = DeleteRRSetChangeInput("some.test.delete.", RecordType.TXT)
|
val delete = DeleteRRSetChangeInput("some.test.delete.", RecordType.TXT, None)
|
||||||
val result = underTestManualEnabled
|
val result = underTestManualEnabled
|
||||||
.buildResponse(
|
.buildResponse(
|
||||||
BatchChangeInput(None, List(apexAddA, onlyBaseAddAAAA, delete), Some("owner-group-ID")),
|
BatchChangeInput(None, List(apexAddA, onlyBaseAddAAAA, delete), Some("owner-group-ID")),
|
||||||
@ -1805,7 +1811,7 @@ class BatchChangeServiceSpec
|
|||||||
}
|
}
|
||||||
|
|
||||||
"return a BatchChangeErrorList if all data inputs are valid/soft failures and manual review is disabled" in {
|
"return a BatchChangeErrorList if all data inputs are valid/soft failures and manual review is disabled" in {
|
||||||
val delete = DeleteRRSetChangeInput("some.test.delete.", RecordType.TXT)
|
val delete = DeleteRRSetChangeInput("some.test.delete.", RecordType.TXT, None)
|
||||||
val result = underTest
|
val result = underTest
|
||||||
.buildResponse(
|
.buildResponse(
|
||||||
BatchChangeInput(None, List(apexAddA, onlyBaseAddAAAA, delete)),
|
BatchChangeInput(None, List(apexAddA, onlyBaseAddAAAA, delete)),
|
||||||
@ -1825,7 +1831,7 @@ class BatchChangeServiceSpec
|
|||||||
|
|
||||||
"return a BatchChangeErrorList if all data inputs are valid/soft failures, scheduled, " +
|
"return a BatchChangeErrorList if all data inputs are valid/soft failures, scheduled, " +
|
||||||
"and manual review is disabled" in {
|
"and manual review is disabled" in {
|
||||||
val delete = DeleteRRSetChangeInput("some.test.delete.", RecordType.TXT)
|
val delete = DeleteRRSetChangeInput("some.test.delete.", RecordType.TXT, None)
|
||||||
val result = underTest
|
val result = underTest
|
||||||
.buildResponse(
|
.buildResponse(
|
||||||
BatchChangeInput(
|
BatchChangeInput(
|
||||||
@ -1872,7 +1878,7 @@ class BatchChangeServiceSpec
|
|||||||
|
|
||||||
"return a BatchChangeErrorList if all data inputs are valid/soft failures, manual review is enabled, " +
|
"return a BatchChangeErrorList if all data inputs are valid/soft failures, manual review is enabled, " +
|
||||||
"but batch change allowManualReview attribute is false" in {
|
"but batch change allowManualReview attribute is false" in {
|
||||||
val delete = DeleteRRSetChangeInput("some.test.delete.", RecordType.TXT)
|
val delete = DeleteRRSetChangeInput("some.test.delete.", RecordType.TXT, None)
|
||||||
val result = underTestManualEnabled
|
val result = underTestManualEnabled
|
||||||
.buildResponse(
|
.buildResponse(
|
||||||
BatchChangeInput(None, List(apexAddA, onlyBaseAddAAAA, delete)),
|
BatchChangeInput(None, List(apexAddA, onlyBaseAddAAAA, delete)),
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1951,19 +1951,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,
|
||||||
@ -2010,7 +2013,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(),
|
||||||
@ -2076,7 +2079,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]
|
||||||
}
|
}
|
||||||
@ -2093,7 +2096,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(
|
||||||
|
@ -127,15 +127,15 @@ class BatchChangeJsonProtocolSpec
|
|||||||
addCNAMEChangeInputJson
|
addCNAMEChangeInputJson
|
||||||
)
|
)
|
||||||
|
|
||||||
val addAChangeInput = AddChangeInput("foo.", A, Some(3600), AData("1.1.1.1"))
|
val addAChangeInput = AddChangeInput("foo.", A, None, Some(3600), AData("1.1.1.1"))
|
||||||
|
|
||||||
val deleteAChangeInput = DeleteRRSetChangeInput("foo.", A)
|
val deleteAChangeInput = DeleteRRSetChangeInput("foo.", A, None)
|
||||||
|
|
||||||
val addAAAAChangeInput = AddChangeInput("bar.", AAAA, Some(1200), AAAAData("1:2:3:4:5:6:7:8"))
|
val addAAAAChangeInput = AddChangeInput("bar.", AAAA, None, Some(1200), AAAAData("1:2:3:4:5:6:7:8"))
|
||||||
|
|
||||||
val addCNAMEChangeInput = AddChangeInput("bizz.baz.", CNAME, Some(200), CNAMEData(Fqdn("buzz.")))
|
val addCNAMEChangeInput = AddChangeInput("bizz.baz.", CNAME, None, Some(200), CNAMEData(Fqdn("buzz.")))
|
||||||
|
|
||||||
val addPTRChangeInput = AddChangeInput("4.5.6.7", PTR, Some(200), PTRData(Fqdn("test.com.")))
|
val addPTRChangeInput = AddChangeInput("4.5.6.7", PTR, None, Some(200), PTRData(Fqdn("test.com.")))
|
||||||
|
|
||||||
val fooDiscoveryError = ZoneDiscoveryError("foo.")
|
val fooDiscoveryError = ZoneDiscoveryError("foo.")
|
||||||
|
|
||||||
@ -211,7 +211,7 @@ class BatchChangeJsonProtocolSpec
|
|||||||
)
|
)
|
||||||
val result = ChangeInputSerializer.fromJson(json).value
|
val result = ChangeInputSerializer.fromJson(json).value
|
||||||
|
|
||||||
result shouldBe AddChangeInput("foo.", A, None, AData("1.1.1.1"))
|
result shouldBe AddChangeInput("foo.", A, None, None, AData("1.1.1.1"))
|
||||||
}
|
}
|
||||||
|
|
||||||
"return an error if the record is not specified for add" in {
|
"return an error if the record is not specified for add" in {
|
||||||
@ -250,7 +250,7 @@ class BatchChangeJsonProtocolSpec
|
|||||||
|
|
||||||
"serializing ChangeInputSerializer to JSON" should {
|
"serializing ChangeInputSerializer to JSON" should {
|
||||||
"successfully serialize valid data for delete" in {
|
"successfully serialize valid data for delete" in {
|
||||||
val deleteChangeInput = DeleteRRSetChangeInput("foo.", A, Some(AData("1.1.1.1")))
|
val deleteChangeInput = DeleteRRSetChangeInput("foo.", A, None, Some(AData("1.1.1.1")))
|
||||||
val json: JObject = buildDeleteRRSetInputJson(Some("foo."), Some(A), Some(AData("1.1.1.1")))
|
val json: JObject = buildDeleteRRSetInputJson(Some("foo."), Some(A), Some(AData("1.1.1.1")))
|
||||||
val result = ChangeInputSerializer.toJson(deleteChangeInput)
|
val result = ChangeInputSerializer.toJson(deleteChangeInput)
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -352,6 +352,10 @@ akka.http {
|
|||||||
# Set to `infinite` to disable.
|
# Set to `infinite` to disable.
|
||||||
bind-timeout = 5s
|
bind-timeout = 5s
|
||||||
|
|
||||||
|
# A default request timeout is applied globally to all routes and can be configured using the
|
||||||
|
# akka.http.server.request-timeout setting (which defaults to 20 seconds).
|
||||||
|
# request-timeout = 60s
|
||||||
|
|
||||||
# Show verbose error messages back to the client
|
# Show verbose error messages back to the client
|
||||||
verbose-error-messages = on
|
verbose-error-messages = on
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
<logger name="com.zaxxer.hikari" level="TRACE">
|
<logger name="com.zaxxer.hikari" level="ERROR">
|
||||||
<appender-ref ref="CONSOLE"/>
|
<appender-ref ref="CONSOLE"/>
|
||||||
</logger>
|
</logger>
|
||||||
|
|
||||||
|
@ -46,11 +46,11 @@ sealed trait SingleChange {
|
|||||||
delete.copy(status = SingleChangeStatus.Failed, systemMessage = Some(error))
|
delete.copy(status = SingleChangeStatus.Failed, systemMessage = Some(error))
|
||||||
}
|
}
|
||||||
|
|
||||||
def withDoesNotExistMessage(error: String): SingleChange = this match {
|
def withDoesNotExistMessage: SingleChange = this match {
|
||||||
case add: SingleAddChange =>
|
case add: SingleAddChange =>
|
||||||
add.copy(status = SingleChangeStatus.Failed, systemMessage = Some(error))
|
add.copy(status = SingleChangeStatus.Failed)
|
||||||
case delete: SingleDeleteRRSetChange =>
|
case delete: SingleDeleteRRSetChange =>
|
||||||
delete.copy(status = SingleChangeStatus.Complete, systemMessage = Some(error))
|
delete.copy(status = SingleChangeStatus.Complete)
|
||||||
}
|
}
|
||||||
|
|
||||||
def withProcessingError(message: Option[String], failedRecordChangeId: String): SingleChange =
|
def withProcessingError(message: Option[String], failedRecordChangeId: String): SingleChange =
|
||||||
|
@ -990,7 +990,10 @@ dotted-hosts = {
|
|||||||
# The time period within which the TCP binding process must be completed.
|
# The time period within which the TCP binding process must be completed.
|
||||||
# Set to `infinite` to disable.
|
# Set to `infinite` to disable.
|
||||||
bind-timeout = 5s
|
bind-timeout = 5s
|
||||||
|
# A default request timeout is applied globally to all routes and can be configured using the
|
||||||
|
# akka.http.server.request-timeout setting (which defaults to 20 seconds).
|
||||||
|
# request-timeout = 60s
|
||||||
|
|
||||||
# Show verbose error messages back to the client
|
# Show verbose error messages back to the client
|
||||||
verbose-error-messages = on
|
verbose-error-messages = on
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -216,13 +216,11 @@ class MySqlRecordChangeRepositoryIntegrationSpec
|
|||||||
}
|
}
|
||||||
saveRecChange.attempt.unsafeRunSync() shouldBe right
|
saveRecChange.attempt.unsafeRunSync() shouldBe right
|
||||||
val page1 = repo.listFailedRecordSetChanges(Some(okZone.id), 2, 0).unsafeRunSync()
|
val page1 = repo.listFailedRecordSetChanges(Some(okZone.id), 2, 0).unsafeRunSync()
|
||||||
println(page1.items)
|
|
||||||
page1.nextId shouldBe 3
|
page1.nextId shouldBe 3
|
||||||
page1.maxItems shouldBe 2
|
page1.maxItems shouldBe 2
|
||||||
(page1.items should contain).theSameElementsInOrderAs(expectedOrder.take(2))
|
(page1.items should contain).theSameElementsInOrderAs(expectedOrder.take(2))
|
||||||
|
|
||||||
val page2 = repo.listFailedRecordSetChanges(Some(okZone.id), 2, page1.nextId).unsafeRunSync()
|
val page2 = repo.listFailedRecordSetChanges(Some(okZone.id), 2, page1.nextId).unsafeRunSync()
|
||||||
println(page2.items)
|
|
||||||
page2.nextId shouldBe 6
|
page2.nextId shouldBe 6
|
||||||
page2.maxItems shouldBe 2
|
page2.maxItems shouldBe 2
|
||||||
(page2.items should contain).theSameElementsInOrderAs(expectedOrder.slice(3, 5))
|
(page2.items should contain).theSameElementsInOrderAs(expectedOrder.slice(3, 5))
|
||||||
|
@ -17,9 +17,9 @@
|
|||||||
package vinyldns.mysql.repository
|
package vinyldns.mysql.repository
|
||||||
|
|
||||||
import java.sql.Timestamp
|
import java.sql.Timestamp
|
||||||
|
|
||||||
import cats.data._
|
import cats.data._
|
||||||
import cats.effect._
|
import cats.effect._
|
||||||
|
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import scalikejdbc._
|
import scalikejdbc._
|
||||||
@ -166,8 +166,8 @@ class MySqlBatchChangeRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
def getBatchFromSingleChangeId(
|
def getBatchFromSingleChangeId(
|
||||||
singleChangeId: String
|
singleChangeId: String
|
||||||
)(implicit s: DBSession): Option[BatchChange] =
|
)(implicit s: DBSession): Option[BatchChange] =
|
||||||
GET_BATCH_CHANGE_METADATA_FROM_SINGLE_CHANGE
|
GET_BATCH_CHANGE_METADATA_FROM_SINGLE_CHANGE
|
||||||
.bind(singleChangeId)
|
.bind(singleChangeId)
|
||||||
.map(extractBatchChange(None))
|
.map(extractBatchChange(None))
|
||||||
@ -181,12 +181,9 @@ class MySqlBatchChangeRepository
|
|||||||
.apply()
|
.apply()
|
||||||
batchMeta.copy(changes = changes)
|
batchMeta.copy(changes = changes)
|
||||||
}
|
}
|
||||||
|
|
||||||
monitor("repo.BatchChangeJDBC.updateSingleChanges") {
|
monitor("repo.BatchChangeJDBC.updateSingleChanges") {
|
||||||
IO {
|
IO {
|
||||||
logger.info(
|
logger.info(s"Updating single change status: ${singleChanges.map(ch => (ch.id, ch.status))}")
|
||||||
s"Updating single change statuses: ${singleChanges.map(ch => (ch.id, ch.status))}"
|
|
||||||
)
|
|
||||||
DB.localTx { implicit s =>
|
DB.localTx { implicit s =>
|
||||||
for {
|
for {
|
||||||
headChange <- singleChanges.headOption
|
headChange <- singleChanges.headOption
|
||||||
@ -194,8 +191,7 @@ class MySqlBatchChangeRepository
|
|||||||
_ = UPDATE_SINGLE_CHANGE.batchByName(batchParams: _*).apply()
|
_ = UPDATE_SINGLE_CHANGE.batchByName(batchParams: _*).apply()
|
||||||
batchChange <- getBatchFromSingleChangeId(headChange.id)
|
batchChange <- getBatchFromSingleChangeId(headChange.id)
|
||||||
} yield batchChange
|
} yield batchChange
|
||||||
}
|
}}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -394,7 +390,6 @@ class MySqlBatchChangeRepository
|
|||||||
case Left(e) => throw e
|
case Left(e) => throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PUT_SINGLE_CHANGE.batchByName(singleChangesParams: _*).apply()
|
PUT_SINGLE_CHANGE.batchByName(singleChangesParams: _*).apply()
|
||||||
batchChange
|
batchChange
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
)
|
)
|
||||||
|
@ -326,7 +326,7 @@
|
|||||||
<label class="batch-change-csv-label btn btn-default" for="batchChangeCsv" id="batchChangeCsvImportLabel">
|
<label class="batch-change-csv-label btn btn-default" for="batchChangeCsv" id="batchChangeCsvImportLabel">
|
||||||
<span><span class="glyphicon glyphicon-import"></span> Import CSV</span>
|
<span><span class="glyphicon glyphicon-import"></span> Import CSV</span>
|
||||||
</label>
|
</label>
|
||||||
<input type="file" id="batchChangeCsv" ng-model="csvInput" name="batchChangeCsv" class="batchChangeCsv" ng-change="uploadCSV(createBatchChangeForm.batchChangeCsv.$viewValue)" batch-change-file>
|
<input type="file" id="batchChangeCsv" ng-model="csvInput" name="batchChangeCsv" class="batchChangeCsv" ng-change="uploadCSV(createBatchChangeForm.batchChangeCsv.$viewValue, batchChangeLimit )" batch-change-file>
|
||||||
<p><a href="https://www.vinyldns.io/portal/dns-changes#dns-change-csv-import" target="_blank" rel="noopener noreferrer">See documentation for sample CSV</a></p>
|
<p><a href="https://www.vinyldns.io/portal/dns-changes#dns-change-csv-import" target="_blank" rel="noopener noreferrer">See documentation for sample CSV</a></p>
|
||||||
</div>
|
</div>
|
||||||
<p ng-if="newBatch.changes.length >= batchChangeLimit">Limit reached. Cannot add more than {{batchChangeLimit}} records per DNS change.</p>
|
<p ng-if="newBatch.changes.length >= batchChangeLimit">Limit reached. Cannot add more than {{batchChangeLimit}} records per DNS change.</p>
|
||||||
@ -336,6 +336,9 @@
|
|||||||
<button type="button" id="create-batch-changes-button" class="btn btn-primary" ng-click="submitChange(manualReviewEnabled)">Submit</button>
|
<button type="button" id="create-batch-changes-button" class="btn btn-primary" ng-click="submitChange(manualReviewEnabled)">Submit</button>
|
||||||
</div>
|
</div>
|
||||||
<div ng-if="formStatus=='pendingConfirm'" class="pull-right">
|
<div ng-if="formStatus=='pendingConfirm'" class="pull-right">
|
||||||
|
<div class="modal fade" id="loader" tabindex="-1" role="dialog" >
|
||||||
|
<div class="spinner" ></div>
|
||||||
|
</div>
|
||||||
<span ng-if="!batchChangeErrors">{{ confirmationPrompt }}</span>
|
<span ng-if="!batchChangeErrors">{{ confirmationPrompt }}</span>
|
||||||
<span ng-if="batchChangeErrors" class="batch-change-error-help">There were errors, please review the highlighted rows and then proceed.</span>
|
<span ng-if="batchChangeErrors" class="batch-change-error-help">There were errors, please review the highlighted rows and then proceed.</span>
|
||||||
<button class="btn btn-default" ng-click="cancelSubmit()">Cancel</button>
|
<button class="btn btn-default" ng-click="cancelSubmit()">Cancel</button>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -282,12 +282,10 @@
|
|||||||
<div class="tab-pane" id="deletedZones">
|
<div class="tab-pane" id="deletedZones">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
|
|
||||||
<!-- SIMPLE DATATABLE -->
|
<!-- SIMPLE DATATABLE -->
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
|
<button id="deleted-zone-refresh-button" class="btn btn-default" ng-click="refreshZones()">
|
||||||
<button id="zone-refresh-button" class="btn btn-default" ng-click="refreshZones()">
|
|
||||||
<span class="fa fa-refresh"></span> Refresh
|
<span class="fa fa-refresh"></span> Refresh
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@ -295,7 +293,7 @@
|
|||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
<form class="input-group" ng-submit="refreshZones()">
|
<form class="input-group" ng-submit="refreshZones()">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<span class="input-group-btn">
|
<span class="input-group-btn" >
|
||||||
<button id="my-deleted-zones-search-button" type="submit" class="btn btn-primary btn-left-round">
|
<button id="my-deleted-zones-search-button" type="submit" class="btn btn-primary btn-left-round">
|
||||||
<span class="fa fa-search"></span>
|
<span class="fa fa-search"></span>
|
||||||
</button>
|
</button>
|
||||||
@ -304,192 +302,184 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<!-- END SEARCH BOX -->
|
|
||||||
|
|
||||||
<!-- DELETED ZONES TABS -->
|
|
||||||
<div class="panel panel-default panel-tabs">
|
|
||||||
<ul class="nav nav-tabs bar_tabs">
|
|
||||||
<li class="active"><a href="#myDeletedZones" data-toggle="tab" ng-click="myZonesAccess()" >My Zones</a></li>
|
|
||||||
<li><a id="tab2-button" href="#allDeletedZones" data-toggle="tab" ng-click="allZonesAccess()">All Zones</a></li>
|
|
||||||
</ul>
|
|
||||||
<div class="panel-body tab-content">
|
|
||||||
<div class="tab-pane active" id="myDeletedZones">
|
|
||||||
<div id="zone-list-table" class="panel-body">
|
|
||||||
<p ng-if="!myDeletedZonesLoaded">Loading my deleted zones...</p>
|
|
||||||
<p ng-if="myDeletedZonesLoaded && !myDeletedZones.length">No zones match the search criteria.</p>
|
|
||||||
|
|
||||||
<!-- PAGINATION -->
|
|
||||||
<div class="dataTables_paginate vinyldns_zones_paginate">
|
|
||||||
<span class="vinyldns_zones_page_number">{{ getZonesPageNumber("myDeletedZones") }}</span>
|
|
||||||
<ul class="pagination">
|
|
||||||
<li class="paginate_button previous">
|
|
||||||
<a ng-if="prevPageEnabled('myDeletedZones')" ng-click="prevPageMyDeletedZones()">Previous</a>
|
|
||||||
</li>
|
|
||||||
<li class="paginate_button next">
|
|
||||||
<a ng-if="nextPageEnabled('myDeletedZones')" ng-click="nextPageMyDeletedZones()">Next</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<!-- END PAGINATION -->
|
|
||||||
|
|
||||||
<table class="table" ng-if="myDeletedZones.length">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Name</th>
|
|
||||||
<th>Email</th>
|
|
||||||
<th>Admin Group</th>
|
|
||||||
<th>Created</th>
|
|
||||||
<th>Abandoned</th>
|
|
||||||
<th>Status</th>
|
|
||||||
<th>Abandoned By</th>
|
|
||||||
@if(meta.sharedDisplayEnabled) {
|
|
||||||
<th>Access</th>
|
|
||||||
}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr ng-repeat="deletedZone in myDeletedZones">
|
|
||||||
<td class="wrap-long-text" ng-bind="deletedZone.zoneChange.zone.name">
|
|
||||||
</td>
|
|
||||||
<td class="wrap-long-text" ng-bind="deletedZone.zoneChange.zone.email">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a ng-if="canAccessGroup(deletedZone.zoneChange.zone.adminGroupId)" ng-bind="deletedZone.adminGroupName"
|
|
||||||
href="/groups/{{deletedZone.zoneChange.zone.adminGroupId}}"></a>
|
|
||||||
<span ng-if="!canAccessGroup(deletedZone.zoneChange.zone.adminGroupId)" ng-bind="deletedZone.adminGroupName"
|
|
||||||
style="line-height: 0"></span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{deletedZone.zoneChange.zone.created}}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{deletedZone.zoneChange.zone.updated}}
|
|
||||||
</td>
|
|
||||||
<td ng-bind="deletedZone.zoneChange.zone.status"></td>
|
|
||||||
<td>
|
|
||||||
{{deletedZone.userName}}
|
|
||||||
</td>
|
|
||||||
@if(meta.sharedDisplayEnabled) {
|
|
||||||
<td>{{zone.shared ? "Shared" : "Private"}}</td>
|
|
||||||
}
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<!-- PAGINATION -->
|
|
||||||
<div class="dataTables_paginate vinyldns_zones_paginate">
|
|
||||||
<span class="vinyldns_zones_page_number">{{ getZonesPageNumber("myDeletedZones") }}</span>
|
|
||||||
<ul class="pagination">
|
|
||||||
<li class="paginate_button previous">
|
|
||||||
<a ng-if="prevPageEnabled('myDeletedZones')" ng-click="prevPageMyDeletedZones()">Previous</a>
|
|
||||||
</li>
|
|
||||||
<li class="paginate_button next">
|
|
||||||
<a ng-if="nextPageEnabled('myDeletedZones')" ng-click="nextPageMyDeletedZones()">Next</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<!-- END PAGINATION -->
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane" id="allDeletedZones">
|
|
||||||
<div id="zone-list-table" class="panel-body">
|
|
||||||
<p ng-if="!allDeletedZonesLoaded">Loading all deleted zones...</p>
|
|
||||||
<p ng-if="allDeletedZonesLoaded && !allDeletedZones.length">No zones match the search criteria.</p>
|
|
||||||
|
|
||||||
<!-- PAGINATION -->
|
|
||||||
<div class="dataTables_paginate vinyldns_zones_paginate">
|
|
||||||
<span class="vinyldns_zones_page_number">{{ getZonesPageNumber("allDeletedZones") }}</span>
|
|
||||||
<ul class="pagination">
|
|
||||||
<li class="paginate_button previous">
|
|
||||||
<a ng-if="prevPageEnabled('allDeletedZones')" ng-click="prevPageAllDeletedZones()">Previous</a>
|
|
||||||
</li>
|
|
||||||
<li class="paginate_button next">
|
|
||||||
<a ng-if="nextPageEnabled('allDeletedZones')" ng-click="nextPageAllDeletedZones()">Next</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<!-- END PAGINATION -->
|
|
||||||
|
|
||||||
<table class="table" ng-if="allDeletedZones.length">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Name</th>
|
|
||||||
<th>Email</th>
|
|
||||||
<th>Admin Group</th>
|
|
||||||
<th>Created</th>
|
|
||||||
<th>Abandoned</th>
|
|
||||||
<th>Status</th>
|
|
||||||
<th>Abandoned By</th>
|
|
||||||
@if(meta.sharedDisplayEnabled) {
|
|
||||||
<th>Access</th>
|
|
||||||
}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr ng-repeat="deletedZone in allDeletedZones">
|
|
||||||
<td class="wrap-long-text" ng-bind="deletedZone.zoneChange.zone.name">
|
|
||||||
</td>
|
|
||||||
<td class="wrap-long-text" ng-bind="deletedZone.zoneChange.zone.email">
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a ng-if="canAccessGroup(deletedZone.zoneChange.zone.adminGroupId)" ng-bind="deletedZone.adminGroupName"
|
|
||||||
href="/groups/{{deletedZone.zoneChange.zone.adminGroupId}}"></a>
|
|
||||||
<span ng-if="!canAccessGroup(deletedZone.zoneChange.zone.adminGroupId)" ng-bind="deletedZone.adminGroupName"
|
|
||||||
style="line-height: 0"></span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{deletedZone.zoneChange.zone.created}}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{deletedZone.zoneChange.zone.updated}}
|
|
||||||
</td>
|
|
||||||
<td ng-bind="deletedZone.zoneChange.zone.status"></td>
|
|
||||||
<td>
|
|
||||||
{{deletedZone.userName}}
|
|
||||||
</td>
|
|
||||||
@if(meta.sharedDisplayEnabled) {
|
|
||||||
<td>{{zone.shared ? "Shared" : "Private"}}</td>
|
|
||||||
}
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<!-- PAGINATION -->
|
|
||||||
<div class="dataTables_paginate vinyldns_zones_paginate">
|
|
||||||
<span class="vinyldns_zones_page_number">{{ getZonesPageNumber("allDeletedZones") }}</span>
|
|
||||||
<ul class="pagination">
|
|
||||||
<li class="paginate_button previous">
|
|
||||||
<a ng-if="prevPageEnabled('allDeletedZones')" ng-click="prevPageAllDeletedZones()">Previous</a>
|
|
||||||
</li>
|
|
||||||
<li class="paginate_button next">
|
|
||||||
<a ng-if="nextPageEnabled('allDeletedZones')" ng-click="nextPageAllDeletedZones()">Next</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<!-- END PAGINATION -->
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- END DELETED ZONES TABS -->
|
|
||||||
</div>
|
</div>
|
||||||
|
<!-- END SEARCH BOX -->
|
||||||
|
|
||||||
|
<!-- DELETED ZONES TABS -->
|
||||||
|
<div class="panel-default panel-tabs">
|
||||||
|
<span class="container-fluid" >
|
||||||
|
<!-- MY DELETED ZONE PAGINATION -->
|
||||||
|
<div class="dataTables_paginate vinyldns_paginate" ng-show="tab == 'myDeletedZones'" >
|
||||||
|
<span class="vinyldns_page_number">{{ getZonesPageNumber("myDeletedZones") }}</span>
|
||||||
|
<ul class="pagination">
|
||||||
|
<li class="vinyldns_paginate_button previous">
|
||||||
|
<a ng-if="prevPageEnabled('myDeletedZones')" ng-click="prevPageMyDeletedZones()">Previous</a>
|
||||||
|
</li>
|
||||||
|
<li class="vinyldns_paginate_button next">
|
||||||
|
<a ng-if="nextPageEnabled('myDeletedZones')" ng-click="nextPageMyDeletedZones()">Next</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<!-- MY DELETED ZONE END PAGINATION -->
|
||||||
|
<!-- ALL DELETED ZONE PAGINATION -->
|
||||||
|
<div class="dataTables_paginate vinyldns_paginate" ng-show="tab == 'allDeletedZones'" >
|
||||||
|
<span class="vinyldns_page_number">{{ getZonesPageNumber("allDeletedZones") }}</span>
|
||||||
|
<ul class="pagination ">
|
||||||
|
<li class="vinyldns_paginate_button previous">
|
||||||
|
<a ng-if="prevPageEnabled('allDeletedZones')" ng-click="prevPageAllDeletedZones()">Previous</a>
|
||||||
|
</li>
|
||||||
|
<li class="vinyldns_paginate_button next">
|
||||||
|
<a ng-if="nextPageEnabled('allDeletedZones')" ng-click="nextPageAllDeletedZones()">Next</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<!--ALL DELETED ZONE END PAGINATION -->
|
||||||
|
<ul class="nav nav-tabs bar_tabs" ng-init = "tab='myDeletedZones'">
|
||||||
|
<li class = "active"><a ng-class="{active: tab == 'myDeletedZones'}" id = "myDeletedZone" href="#myDeletedZones" data-toggle="tab" ng-click="tab = 'myDeletedZones'; myZonesAccess()" >My Zones</a></li>
|
||||||
|
<li><a ng-class="{active: tab == 'allDeletedZones'}" href="#allDeletedZones" data-toggle="tab" ng-click="tab = 'allDeletedZones'; allZonesAccess()" >All Zones</a></li>
|
||||||
|
</ul>
|
||||||
|
<div class="tab-content">
|
||||||
|
<div class="tab-pane active" id="myDeletedZones">
|
||||||
|
<div id="my-deleted-zone-list-table" class="panel-body">
|
||||||
|
<p ng-if="!myDeletedZonesLoaded">Loading my deleted zones...</p>
|
||||||
|
<p ng-if="myDeletedZonesLoaded && !myDeletedZones.length">No zones match the search criteria.</p>
|
||||||
|
<table class="table" ng-if="myDeletedZones.length">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Email</th>
|
||||||
|
<th>Admin Group</th>
|
||||||
|
<th>Created</th>
|
||||||
|
<th>Abandoned</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Abandoned By</th>
|
||||||
|
@if(meta.sharedDisplayEnabled) {
|
||||||
|
<th>Access</th>
|
||||||
|
}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr ng-repeat="deletedZone in myDeletedZones">
|
||||||
|
<td class="wrap-long-text" ng-bind="deletedZone.zoneChange.zone.name">
|
||||||
|
</td>
|
||||||
|
<td class="wrap-long-text" ng-bind="deletedZone.zoneChange.zone.email">
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a ng-if="canAccessGroup(deletedZone.zoneChange.zone.adminGroupId)" ng-bind="deletedZone.adminGroupName"
|
||||||
|
href="/groups/{{deletedZone.zoneChange.zone.adminGroupId}}"></a>
|
||||||
|
<span ng-if="!canAccessGroup(deletedZone.zoneChange.zone.adminGroupId)" ng-bind="deletedZone.adminGroupName"
|
||||||
|
style="line-height: 0"></span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{deletedZone.zoneChange.zone.created}}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{deletedZone.zoneChange.zone.updated}}
|
||||||
|
</td>
|
||||||
|
<td ng-bind="deletedZone.zoneChange.zone.status"></td>
|
||||||
|
<td>
|
||||||
|
{{deletedZone.userName}}
|
||||||
|
</td>
|
||||||
|
@if(meta.sharedDisplayEnabled) {
|
||||||
|
<td>{{deletedZone.zoneChange.zone.shared ? "Shared" : "Private"}}</td>
|
||||||
|
}
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- PAGINATION -->
|
||||||
|
<div class="dataTables_paginate vinyldns_paginate">
|
||||||
|
<span class="vinyldns_page_number">{{ getZonesPageNumber("myDeletedZones") }}</span>
|
||||||
|
<ul class="pagination">
|
||||||
|
<li class="paginate_button previous">
|
||||||
|
<a ng-if="prevPageEnabled('myDeletedZones')" ng-click="prevPageMyDeletedZones()">Previous</a>
|
||||||
|
</li>
|
||||||
|
<li class="paginate_button next">
|
||||||
|
<a ng-if="nextPageEnabled('myDeletedZones')" ng-click="nextPageMyDeletedZones()">Next</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<!-- END PAGINATION -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane" id="allDeletedZones">
|
||||||
|
<div id="all-deleted-zone-list-table" class="panel-body">
|
||||||
|
<p ng-if="!allDeletedZonesLoaded">Loading all deleted zones...</p>
|
||||||
|
<p ng-if="allDeletedZonesLoaded && !allDeletedZones.length">No zones match the search criteria.</p>
|
||||||
|
<table class="table" ng-if="allDeletedZones.length">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Email</th>
|
||||||
|
<th>Admin Group</th>
|
||||||
|
<th>Created</th>
|
||||||
|
<th>Abandoned</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Abandoned By</th>
|
||||||
|
@if(meta.sharedDisplayEnabled) {
|
||||||
|
<th>Access</th>
|
||||||
|
}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr ng-repeat="deletedZone in allDeletedZones">
|
||||||
|
<td class="wrap-long-text" ng-bind="deletedZone.zoneChange.zone.name">
|
||||||
|
</td>
|
||||||
|
<td class="wrap-long-text" ng-bind="deletedZone.zoneChange.zone.email">
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a ng-if="canAccessGroup(deletedZone.zoneChange.zone.adminGroupId)" ng-bind="deletedZone.adminGroupName"
|
||||||
|
href="/groups/{{deletedZone.zoneChange.zone.adminGroupId}}"></a>
|
||||||
|
<span ng-if="!canAccessGroup(deletedZone.zoneChange.zone.adminGroupId)" ng-bind="deletedZone.adminGroupName"
|
||||||
|
style="line-height: 0"></span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{deletedZone.zoneChange.zone.created}}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{deletedZone.zoneChange.zone.updated}}
|
||||||
|
</td>
|
||||||
|
<td ng-bind="deletedZone.zoneChange.zone.status"></td>
|
||||||
|
<td>
|
||||||
|
{{deletedZone.userName}}
|
||||||
|
</td>
|
||||||
|
@if(meta.sharedDisplayEnabled) {
|
||||||
|
<td>{{deletedZone.zoneChange.zone.shared ? "Shared" : "Private"}}</td>
|
||||||
|
}
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- PAGINATION -->
|
||||||
|
<div class="dataTables_paginate vinyldns_paginate">
|
||||||
|
<span class="vinyldns_page_number">{{ getZonesPageNumber("allDeletedZones") }}</span>
|
||||||
|
<ul class="pagination">
|
||||||
|
<li class="paginate_button previous">
|
||||||
|
<a ng-if="prevPageEnabled('allDeletedZones')" ng-click="prevPageAllDeletedZones()">Previous</a>
|
||||||
|
</li>
|
||||||
|
<li class="paginate_button next">
|
||||||
|
<a ng-if="nextPageEnabled('allDeletedZones')" ng-click="nextPageAllDeletedZones()">Next</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<!-- END PAGINATION -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<!-- END DELETED ZONES TABS -->
|
||||||
<div class="panel-footer"></div>
|
<div class="panel-footer"></div>
|
||||||
</div>
|
</div>
|
||||||
<!-- END SIMPLE DATATABLE -->
|
<!-- END SIMPLE DATATABLE -->
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- END VERTICAL TABS -->
|
<!-- END VERTICAL TABS -->
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<!-- END PAGE CONTENT WRAPPER -->
|
<!-- END PAGE CONTENT WRAPPER -->
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<!-- END PAGE CONTENT -->
|
<!-- END PAGE CONTENT -->
|
||||||
|
|
||||||
|
@ -88,6 +88,9 @@ api {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Batch change limit
|
||||||
|
batch-change-limit = 1000
|
||||||
|
|
||||||
http.port = 9001
|
http.port = 9001
|
||||||
http.port = ${?PORTAL_PORT}
|
http.port = ${?PORTAL_PORT}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
<logger name="play" level="INFO" />
|
<logger name="play" level="INFO" />
|
||||||
<logger name="application" level="DEBUG" />
|
<logger name="application" level="DEBUG" />
|
||||||
|
|
||||||
<logger name="com.zaxxer.hikari" level="TRACE">
|
<logger name="com.zaxxer.hikari" level="ERROR">
|
||||||
<appender-ref ref="CONSOLE"/>
|
<appender-ref ref="CONSOLE"/>
|
||||||
</logger>
|
</logger>
|
||||||
|
|
||||||
|
@ -462,6 +462,10 @@ input[type="file"] {
|
|||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vinyldns_paginate_button {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.no-top-margin {
|
.no-top-margin {
|
||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
}
|
}
|
||||||
|
@ -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');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,7 @@
|
|||||||
$scope.manualReviewEnabled;
|
$scope.manualReviewEnabled;
|
||||||
$scope.naptrFlags = ["U", "S", "A", "P"];
|
$scope.naptrFlags = ["U", "S", "A", "P"];
|
||||||
|
|
||||||
|
|
||||||
$scope.addSingleChange = function() {
|
$scope.addSingleChange = function() {
|
||||||
$scope.newBatch.changes.push({changeType: "Add", type: "A+PTR"});
|
$scope.newBatch.changes.push({changeType: "Add", type: "A+PTR"});
|
||||||
var changesLength = $scope.newBatch.changes.length;
|
var changesLength = $scope.newBatch.changes.length;
|
||||||
@ -116,7 +117,7 @@
|
|||||||
$scope.alerts.push(alert);
|
$scope.alerts.push(alert);
|
||||||
$timeout(function(){
|
$timeout(function(){
|
||||||
location.href = "/dnschanges/" + response.data.id;
|
location.href = "/dnschanges/" + response.data.id;
|
||||||
}, 2000);
|
}, 2000);
|
||||||
$scope.batch = response.data;
|
$scope.batch = response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,14 +162,14 @@
|
|||||||
$scope.alerts.push(alert);
|
$scope.alerts.push(alert);
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.uploadCSV = function(file) {
|
$scope.uploadCSV = function(file, batchChangeLimit) {
|
||||||
parseFile(file).then(function(dataLength){
|
parseFile(file, batchChangeLimit).then(function(dataLength){
|
||||||
$scope.alerts.push({type: 'success', content: 'Successfully imported ' + dataLength + ' changes.' });
|
$scope.alerts.push({type: 'success', content: 'Successfully imported ' + dataLength + ' DNS changes.' });
|
||||||
}, function(error) {
|
}, function(error) {
|
||||||
$scope.alerts.push({type: 'danger', content: error});
|
$scope.alerts.push({type: 'danger', content: error});
|
||||||
});
|
});
|
||||||
|
|
||||||
function parseFile(file) {
|
function parseFile(file, batchChangeLimit) {
|
||||||
return $q(function(resolve, reject) {
|
return $q(function(resolve, reject) {
|
||||||
if (!file.name.endsWith('.csv')) {
|
if (!file.name.endsWith('.csv')) {
|
||||||
reject("Import failed. File should be of ‘.csv’ type.");
|
reject("Import failed. File should be of ‘.csv’ type.");
|
||||||
@ -177,6 +178,9 @@
|
|||||||
var reader = new FileReader();
|
var reader = new FileReader();
|
||||||
reader.onload = function(e) {
|
reader.onload = function(e) {
|
||||||
var rows = e.target.result.split("\n");
|
var rows = e.target.result.split("\n");
|
||||||
|
if(rows.length - 1 > batchChangeLimit)
|
||||||
|
{reject("Import failed. Cannot add more than " + batchChangeLimit + " records per DNS change.");
|
||||||
|
} else {
|
||||||
if (rows[0].trim() == "Change Type,Record Type,Input Name,TTL,Record Data") {
|
if (rows[0].trim() == "Change Type,Record Type,Input Name,TTL,Record Data") {
|
||||||
$scope.newBatch.changes = [];
|
$scope.newBatch.changes = [];
|
||||||
for(var i = 1; i < rows.length; i++) {
|
for(var i = 1; i < rows.length; i++) {
|
||||||
@ -186,10 +190,10 @@
|
|||||||
}
|
}
|
||||||
$scope.$apply()
|
$scope.$apply()
|
||||||
resolve($scope.newBatch.changes.length);
|
resolve($scope.newBatch.changes.length);
|
||||||
} else {
|
} else {
|
||||||
reject("Import failed. CSV header must be: Change Type,Record Type,Input Name,TTL,Record Data");
|
reject("Import failed. CSV header must be: Change Type,Record Type,Input Name,TTL,Record Data");
|
||||||
}
|
}
|
||||||
}
|
}}
|
||||||
reader.readAsText(file);
|
reader.readAsText(file);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -30,7 +30,24 @@
|
|||||||
"allowManualReview": allowManualReview
|
"allowManualReview": allowManualReview
|
||||||
}
|
}
|
||||||
var url = utilityService.urlBuilder('/api/dnschanges', params);
|
var url = utilityService.urlBuilder('/api/dnschanges', params);
|
||||||
return $http.post(url, data, {headers: utilityService.getCsrfHeader()});
|
let loader = $("#loader");
|
||||||
|
loader.modal({
|
||||||
|
backdrop: "static",
|
||||||
|
keyboard: false, //remove option to close with keyboard
|
||||||
|
show: true //Display loader!
|
||||||
|
})
|
||||||
|
let promis = $http.post(url, data, {headers: utilityService.getCsrfHeader()});
|
||||||
|
|
||||||
|
function hideLoader() {
|
||||||
|
loader.modal("hide");
|
||||||
|
|
||||||
|
// Manually remove the backdrop after the modal is hidden
|
||||||
|
$('.modal-backdrop').remove();
|
||||||
|
$('body').removeClass('modal-open'); // Remove the class that prevents scrolling
|
||||||
|
}
|
||||||
|
// Hide loader when api gets response
|
||||||
|
promis.then(hideLoader, hideLoader).catch(hideLoader).finally(hideLoader);
|
||||||
|
return promis
|
||||||
};
|
};
|
||||||
|
|
||||||
this.getBatchChanges = function (maxItems, startFrom, ignoreAccess, approvalStatus, userName, dateTimeRangeStart, dateTimeRangeEnd) {
|
this.getBatchChanges = function (maxItems, startFrom, ignoreAccess, approvalStatus, userName, dateTimeRangeStart, dateTimeRangeEnd) {
|
||||||
|
@ -279,7 +279,7 @@
|
|||||||
$scope.changeHistoryPrevPage = function() {
|
$scope.changeHistoryPrevPage = function() {
|
||||||
var startFrom = pagingService.getPrevStartFrom(changePaging);
|
var startFrom = pagingService.getPrevStartFrom(changePaging);
|
||||||
return recordsService
|
return recordsService
|
||||||
.listRecordSetChangeHistory(changePaging.maxItems, startFrom, $scope.recordFqdn, $scope.recordType)
|
.listRecordSetChangeHistory($scope.zoneId, changePaging.maxItems, startFrom, $scope.recordFqdn, $scope.recordType)
|
||||||
.then(function(response) {
|
.then(function(response) {
|
||||||
changePaging = pagingService.prevPageUpdate(response.data.nextId, changePaging);
|
changePaging = pagingService.prevPageUpdate(response.data.nextId, changePaging);
|
||||||
updateChangeDisplay(response.data.recordSetChanges);
|
updateChangeDisplay(response.data.recordSetChanges);
|
||||||
@ -291,7 +291,7 @@
|
|||||||
|
|
||||||
$scope.changeHistoryNextPage = function() {
|
$scope.changeHistoryNextPage = function() {
|
||||||
return recordsService
|
return recordsService
|
||||||
.listRecordSetChangeHistory(changePaging.maxItems, changePaging.next, $scope.recordFqdn, $scope.recordType)
|
.listRecordSetChangeHistory($scope.zoneId, changePaging.maxItems, changePaging.next, $scope.recordFqdn, $scope.recordType)
|
||||||
.then(function(response) {
|
.then(function(response) {
|
||||||
var changes = response.data.recordSetChanges;
|
var changes = response.data.recordSetChanges;
|
||||||
changePaging = pagingService.nextPageUpdate(changes, response.data.nextId, changePaging);
|
changePaging = pagingService.nextPageUpdate(changes, response.data.nextId, changePaging);
|
||||||
|
@ -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) {
|
||||||
|
@ -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"
|
||||||
|
@ -302,6 +302,10 @@ akka.http {
|
|||||||
# Set to `infinite` to disable.
|
# Set to `infinite` to disable.
|
||||||
bind-timeout = 5s
|
bind-timeout = 5s
|
||||||
|
|
||||||
|
# A default request timeout is applied globally to all routes and can be configured using the
|
||||||
|
# akka.http.server.request-timeout setting (which defaults to 20 seconds).
|
||||||
|
# request-timeout = 60s
|
||||||
|
|
||||||
# Show verbose error messages back to the client
|
# Show verbose error messages back to the client
|
||||||
verbose-error-messages = on
|
verbose-error-messages = on
|
||||||
}
|
}
|
||||||
|
@ -360,6 +360,10 @@ akka.http {
|
|||||||
# Set to `infinite` to disable.
|
# Set to `infinite` to disable.
|
||||||
bind-timeout = 5s
|
bind-timeout = 5s
|
||||||
|
|
||||||
|
# A default request timeout is applied globally to all routes and can be configured using the
|
||||||
|
# akka.http.server.request-timeout setting (which defaults to 20 seconds).
|
||||||
|
# request-timeout = 60s
|
||||||
|
|
||||||
# Show verbose error messages back to the client
|
# Show verbose error messages back to the client
|
||||||
verbose-error-messages = on
|
verbose-error-messages = on
|
||||||
}
|
}
|
||||||
|
@ -1 +1 @@
|
|||||||
version in ThisBuild := "0.20.0"
|
version in ThisBuild := "0.20.2"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user