fix pdns api clients (retry 19x) and compose update.

This commit is contained in:
dalbodeule 2024-06-07 15:16:42 +09:00
parent 506d62e5f0
commit 25650b2c16
No known key found for this signature in database
GPG Key ID: EFA860D069C9FA65
8 changed files with 98 additions and 156 deletions

View File

@ -95,6 +95,7 @@ services:
nginx: nginx:
image: jonasal/nginx-certbot:latest image: jonasal/nginx-certbot:latest
container_name: pdns-nginx
restart: always restart: always
environment: environment:
CERTBOT_EMAIL: ${CERTBOT_EMAIL} CERTBOT_EMAIL: ${CERTBOT_EMAIL}

View File

@ -54,6 +54,7 @@ services:
restart: always restart: always
nginx: nginx:
image: jonasal/nginx-certbot:latest image: jonasal/nginx-certbot:latest
container_name: pdns-nginx
restart: always restart: always
environment: environment:
CERTBOT_EMAIL: ${CERTBOT_EMAIL} CERTBOT_EMAIL: ${CERTBOT_EMAIL}

View File

@ -0,0 +1,28 @@
package space.mori.dnsapi
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.RestControllerAdvice
import space.mori.dnsapi.dto.ApiResponseDTO
import space.mori.dnsapi.dto.ErrorOrMessage
@RestControllerAdvice
class GlobalExceptionHandler {
@ExceptionHandler(PowerDNSAPIException::class)
fun handlePowerDNSAPIException(ex: PowerDNSAPIException): ResponseEntity<ApiResponseDTO<Nothing>> {
var idx = 0
val errors = mutableListOf(ErrorOrMessage(idx, ex.message ?: ""))
ex.errors?.forEach{
errors.add(ErrorOrMessage(idx++, it))
}
val response = ApiResponseDTO(
success = false,
errors = errors,
result = null
)
return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).body(response)
}
}

View File

@ -29,7 +29,7 @@ class PowerDNSAPIClient() {
@Throws(PowerDNSAPIException::class) @Throws(PowerDNSAPIException::class)
fun createZone(zoneName: String): Response { fun createZone(zoneName: String): Response {
val body = gson.toJson(mapOf( val body = gson.toJson(mapOf(
"name" to zoneName, "name" to "$zoneName.",
"nameservers" to nameserver.split(",")) "nameservers" to nameserver.split(","))
).toRequestBody() ).toRequestBody()
val request = Request.Builder() val request = Request.Builder()
@ -51,7 +51,7 @@ class PowerDNSAPIClient() {
@Throws(PowerDNSAPIException::class) @Throws(PowerDNSAPIException::class)
fun deleteZone(zoneName: String): Response { fun deleteZone(zoneName: String): Response {
val request = Request.Builder() val request = Request.Builder()
.url("$apiUrl/api/v1/servers/localhost/zones/$zoneName") .url("$apiUrl/api/v1/servers/localhost/zones/$zoneName.")
.addHeader("X-API-Key", apiKey) .addHeader("X-API-Key", apiKey)
.addHeader("Accept", "application/json") .addHeader("Accept", "application/json")
.addHeader("Content-Type", "application/json") .addHeader("Content-Type", "application/json")
@ -74,7 +74,7 @@ class PowerDNSAPIClient() {
"content" to recordContent "content" to recordContent
)).toRequestBody("application/json".toMediaType()) )).toRequestBody("application/json".toMediaType())
val request = Request.Builder() val request = Request.Builder()
.url("$apiUrl/api/v1/servers/localhost/zones/$zoneName/records") .url("$apiUrl/api/v1/servers/localhost/zones/$zoneName./records")
.addHeader("X-API-Key", apiKey) .addHeader("X-API-Key", apiKey)
.addHeader("Accept", "application/json") .addHeader("Accept", "application/json")
.addHeader("Content-Type", "application/json") .addHeader("Content-Type", "application/json")
@ -95,7 +95,7 @@ class PowerDNSAPIClient() {
"content" to recordContent "content" to recordContent
)).toRequestBody("application/json".toMediaType()) )).toRequestBody("application/json".toMediaType())
val request = Request.Builder() val request = Request.Builder()
.url("$apiUrl/api/v1/servers/localhost/zones/$zoneName/records/$recordName/$recordType") .url("$apiUrl/api/v1/servers/localhost/zones/$zoneName./records/$recordName/$recordType")
.addHeader("X-API-Key", apiKey) .addHeader("X-API-Key", apiKey)
.addHeader("Accept", "application/json") .addHeader("Accept", "application/json")
.addHeader("Content-Type", "application/json") .addHeader("Content-Type", "application/json")
@ -113,7 +113,7 @@ class PowerDNSAPIClient() {
@Throws(PowerDNSAPIException::class) @Throws(PowerDNSAPIException::class)
fun deleteRecord(zoneName: String, recordName: String, recordType: String): Response { fun deleteRecord(zoneName: String, recordName: String, recordType: String): Response {
val request = Request.Builder() val request = Request.Builder()
.url("$apiUrl/api/v1/servers/localhost/zones/$zoneName/records/$recordName/$recordType") .url("$apiUrl/api/v1/servers/localhost/zones/$zoneName./records/$recordName/$recordType")
.addHeader("X-API-Key", apiKey) .addHeader("X-API-Key", apiKey)
.addHeader("Accept", "application/json") .addHeader("Accept", "application/json")
.addHeader("Content-Type", "application/json") .addHeader("Content-Type", "application/json")
@ -132,7 +132,7 @@ class PowerDNSAPIClient() {
@ReflectiveAccess @ReflectiveAccess
data class PowerDNSAPIError( data class PowerDNSAPIError(
@SerializedName("error") val error: String, @SerializedName("error") val error: String,
@SerializedName("errors") val errors: List<String> @SerializedName("errors") val errors: List<String>?
) )
class PowerDNSAPIErrorDeserializer : JsonDeserializer<PowerDNSAPIError?> { class PowerDNSAPIErrorDeserializer : JsonDeserializer<PowerDNSAPIError?> {
@ -150,6 +150,8 @@ class PowerDNSAPIErrorDeserializer : JsonDeserializer<PowerDNSAPIError?> {
} }
class PowerDNSAPIException(private val error: PowerDNSAPIError): RuntimeException(error.error) { class PowerDNSAPIException(private val error: PowerDNSAPIError): RuntimeException(error.error) {
val errors: List<String> val errors: List<String>?
get() = error.errors get() = error.errors
override fun toString(): String = "PowerDNSAPIException(${error.error} ${errors?.joinToString(", ") ?: ""})"
} }

View File

@ -6,10 +6,7 @@ import io.swagger.v3.oas.annotations.media.Schema
import io.swagger.v3.oas.annotations.responses.ApiResponse import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.responses.ApiResponses import io.swagger.v3.oas.annotations.responses.ApiResponses
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.* import org.springframework.web.bind.annotation.*
import org.springframework.web.server.ResponseStatusException
import space.mori.dnsapi.PowerDNSAPIException
import space.mori.dnsapi.db.Domain import space.mori.dnsapi.db.Domain
import space.mori.dnsapi.dto.* import space.mori.dnsapi.dto.*
import space.mori.dnsapi.service.DomainService import space.mori.dnsapi.service.DomainService
@ -29,19 +26,7 @@ class DomainController(
content = [Content(schema = Schema(implementation = ApiResponseDTO::class))]) content = [Content(schema = Schema(implementation = ApiResponseDTO::class))])
]) ])
fun allDomains(): ApiResponseDTO<List<DomainResponseDTO?>> { fun allDomains(): ApiResponseDTO<List<DomainResponseDTO?>> {
try { return ApiResponseDTO(result = domainService.getAllDomains().map { it.toDTO() })
return ApiResponseDTO(result = domainService.getAllDomains().map { it.toDTO() })
} catch(e : PowerDNSAPIException) {
var idx = 0
val errors = mutableListOf(ErrorOrMessage(idx, e.message ?: ""))
e.errors.forEach{
errors.add(ErrorOrMessage(idx++, it))
}
throw ResponseStatusException(HttpStatus.EXPECTATION_FAILED,
ApiResponseDTO(false, errors = errors, result = listOf(null)).toString()
)
}
} }
@Operation(summary = "Get domain", tags = ["domain"]) @Operation(summary = "Get domain", tags = ["domain"])
@ -54,19 +39,7 @@ class DomainController(
fun getDomainByCfid( fun getDomainByCfid(
@PathVariable cfid: String? @PathVariable cfid: String?
): ApiResponseDTO<DomainResponseDTO> { ): ApiResponseDTO<DomainResponseDTO> {
try { return ApiResponseDTO(result = domainService.getDomainById(cfid!!).toDTO())
return ApiResponseDTO(result = domainService.getDomainById(cfid!!).toDTO())
} catch(e : PowerDNSAPIException) {
var idx = 0
val errors = mutableListOf(ErrorOrMessage(idx, e.message ?: ""))
e.errors.forEach{
errors.add(ErrorOrMessage(idx++, it))
}
throw ResponseStatusException(HttpStatus.EXPECTATION_FAILED,
ApiResponseDTO(false, errors = errors, result = listOf(null)).toString()
)
}
} }
@Operation(summary = "Create domain", tags = ["domain"]) @Operation(summary = "Create domain", tags = ["domain"])
@ -77,19 +50,7 @@ class DomainController(
]) ])
@PostMapping @PostMapping
fun createDomain(@RequestBody domain: DomainRequestDTO): ApiResponseDTO<DomainResponseDTO> { fun createDomain(@RequestBody domain: DomainRequestDTO): ApiResponseDTO<DomainResponseDTO> {
try { return ApiResponseDTO(result = domainService.createDomain(domain).toDTO())
return ApiResponseDTO(result = domainService.createDomain(domain).toDTO())
} catch(e : PowerDNSAPIException) {
var idx = 0
val errors = mutableListOf(ErrorOrMessage(idx, e.message ?: ""))
e.errors.forEach{
errors.add(ErrorOrMessage(idx++, it))
}
throw ResponseStatusException(HttpStatus.EXPECTATION_FAILED,
ApiResponseDTO(false, errors = errors, result = listOf(null)).toString()
)
}
} }
@Operation(summary = "Delete domain", tags = ["domain"]) @Operation(summary = "Delete domain", tags = ["domain"])
@ -100,21 +61,9 @@ class DomainController(
]) ])
@DeleteMapping("/{domain_id}") @DeleteMapping("/{domain_id}")
fun deleteDomain(@PathVariable domain_id: String?): ApiResponseDTO<DeleteResponseWithId> { fun deleteDomain(@PathVariable domain_id: String?): ApiResponseDTO<DeleteResponseWithId> {
try { domainService.deleteDomain(domain_id!!)
domainService.deleteDomain(domain_id!!)
return ApiResponseDTO(result = DeleteResponseWithId(domain_id)) return ApiResponseDTO(result = DeleteResponseWithId(domain_id))
} catch (e: PowerDNSAPIException) {
var idx = 0
val errors = mutableListOf(ErrorOrMessage(idx, e.message ?: ""))
e.errors.forEach{
errors.add(ErrorOrMessage(idx++, it))
}
throw ResponseStatusException(HttpStatus.EXPECTATION_FAILED,
ApiResponseDTO(false, errors = errors, result = listOf(null)).toString()
)
}
} }
private fun Domain.toDTO() = DomainResponseDTO(id = cfid, name = name) private fun Domain.toDTO() = DomainResponseDTO(id = cfid, name = name)

View File

@ -6,10 +6,7 @@ import io.swagger.v3.oas.annotations.media.Schema
import io.swagger.v3.oas.annotations.responses.ApiResponse import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.responses.ApiResponses import io.swagger.v3.oas.annotations.responses.ApiResponses
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.* import org.springframework.web.bind.annotation.*
import org.springframework.web.server.ResponseStatusException
import space.mori.dnsapi.PowerDNSAPIException
import space.mori.dnsapi.dto.* import space.mori.dnsapi.dto.*
import space.mori.dnsapi.service.RecordService import space.mori.dnsapi.service.RecordService
@ -27,19 +24,7 @@ class RecordController(
content = [Content(schema = Schema(implementation = ApiResponseDTO::class))]), content = [Content(schema = Schema(implementation = ApiResponseDTO::class))]),
]) ])
fun allRecords(@PathVariable zone_id: String): ApiResponseDTO<List<RecordResponseDTO>> { fun allRecords(@PathVariable zone_id: String): ApiResponseDTO<List<RecordResponseDTO>> {
try { return ApiResponseDTO(result = recordService.getRecordsByDomain(zone_id)?.map{ it } ?: listOf())
return ApiResponseDTO(result = recordService.getRecordsByDomain(zone_id)?.map{ it } ?: listOf())
} catch(e : PowerDNSAPIException) {
var idx = 0
val errors = mutableListOf(ErrorOrMessage(idx, e.message ?: ""))
e.errors.forEach{
errors.add(ErrorOrMessage(idx++, it))
}
throw ResponseStatusException(HttpStatus.EXPECTATION_FAILED,
ApiResponseDTO(false, errors = errors, result = listOf(null)).toString()
)
}
} }
@GetMapping("{zone_id}/dns_records/{dns_record_id}") @GetMapping("{zone_id}/dns_records/{dns_record_id}")
@ -50,19 +35,7 @@ class RecordController(
content = [Content(schema = Schema(implementation = ApiResponseDTO::class))]), content = [Content(schema = Schema(implementation = ApiResponseDTO::class))]),
]) ])
fun getRecordByCfid(@PathVariable zone_id: String, @PathVariable dns_record_id: String): ApiResponseDTO<RecordResponseDTO> { fun getRecordByCfid(@PathVariable zone_id: String, @PathVariable dns_record_id: String): ApiResponseDTO<RecordResponseDTO> {
try { return ApiResponseDTO(result = recordService.getRecord(zone_id, dns_record_id))
return ApiResponseDTO(result = recordService.getRecord(zone_id, dns_record_id))
} catch(e : PowerDNSAPIException) {
var idx = 0
val errors = mutableListOf(ErrorOrMessage(idx, e.message ?: ""))
e.errors.forEach{
errors.add(ErrorOrMessage(idx++, it))
}
throw ResponseStatusException(HttpStatus.EXPECTATION_FAILED,
ApiResponseDTO(false, errors = errors, result = listOf(null)).toString()
)
}
} }
@PostMapping("{zone_id}/dns_records") @PostMapping("{zone_id}/dns_records")
@ -73,19 +46,7 @@ class RecordController(
content = [Content(schema = Schema(implementation = ApiResponseDTO::class))]), content = [Content(schema = Schema(implementation = ApiResponseDTO::class))]),
]) ])
fun createRecord(@PathVariable zone_id: String, @RequestBody record: RecordRequestDTO): ApiResponseDTO<RecordResponseDTO> { fun createRecord(@PathVariable zone_id: String, @RequestBody record: RecordRequestDTO): ApiResponseDTO<RecordResponseDTO> {
try { return ApiResponseDTO(result = recordService.createRecord(zone_id, record))
return ApiResponseDTO(result = recordService.createRecord(zone_id, record))
} catch(e : PowerDNSAPIException) {
var idx = 0
val errors = mutableListOf(ErrorOrMessage(idx, e.message ?: ""))
e.errors.forEach{
errors.add(ErrorOrMessage(idx++, it))
}
throw ResponseStatusException(HttpStatus.EXPECTATION_FAILED,
ApiResponseDTO(false, errors = errors, result = listOf(null)).toString()
)
}
} }
@DeleteMapping("{zone_id}/dns_records/{dns_record_id}") @DeleteMapping("{zone_id}/dns_records/{dns_record_id}")
@ -96,20 +57,8 @@ class RecordController(
content = [Content(schema = Schema(implementation = ApiResponseDTO::class))]), content = [Content(schema = Schema(implementation = ApiResponseDTO::class))]),
]) ])
fun deleteRecord(@PathVariable zone_id: String, @PathVariable dns_record_id: String): ApiResponseDTO<DeleteResponseWithId> { fun deleteRecord(@PathVariable zone_id: String, @PathVariable dns_record_id: String): ApiResponseDTO<DeleteResponseWithId> {
try { val record_id = recordService.deleteRecord(zone_id, dns_record_id)
val record_id = recordService.deleteRecord(zone_id, dns_record_id) return ApiResponseDTO(result = DeleteResponseWithId(record_id))
return ApiResponseDTO(result = DeleteResponseWithId(record_id))
} catch(e : PowerDNSAPIException) {
var idx = 0
val errors = mutableListOf(ErrorOrMessage(idx, e.message ?: ""))
e.errors.forEach{
errors.add(ErrorOrMessage(idx++, it))
}
throw ResponseStatusException(HttpStatus.EXPECTATION_FAILED,
ApiResponseDTO(false, errors = errors, result = listOf(null)).toString()
)
}
} }
@PatchMapping("{zone_id}/dns_records/{dns_record_id}") @PatchMapping("{zone_id}/dns_records/{dns_record_id}")
@ -120,18 +69,6 @@ class RecordController(
content = [Content(schema = Schema(implementation = ApiResponseDTO::class))]), content = [Content(schema = Schema(implementation = ApiResponseDTO::class))]),
]) ])
fun updateRecord(@PathVariable zone_id: String, @PathVariable dns_record_id: String, @RequestBody record: RecordRequestDTO): ApiResponseDTO<RecordResponseDTO> { fun updateRecord(@PathVariable zone_id: String, @PathVariable dns_record_id: String, @RequestBody record: RecordRequestDTO): ApiResponseDTO<RecordResponseDTO> {
try { return ApiResponseDTO(result = recordService.updateRecord(zone_id, dns_record_id, record))
return ApiResponseDTO(result = recordService.updateRecord(zone_id, dns_record_id, record))
} catch(e : PowerDNSAPIException) {
var idx = 0
val errors = mutableListOf(ErrorOrMessage(idx, e.message ?: ""))
e.errors.forEach{
errors.add(ErrorOrMessage(idx++, it))
}
throw ResponseStatusException(HttpStatus.EXPECTATION_FAILED,
ApiResponseDTO(false, errors = errors, result = listOf(null)).toString()
)
}
} }
} }

View File

@ -1,16 +1,23 @@
package space.mori.dnsapi.dto package space.mori.dnsapi.dto
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import com.google.gson.annotations.SerializedName
val gson = GsonBuilder().setPrettyPrinting().create() val gson = GsonBuilder().setPrettyPrinting().create()
data class ApiResponseDTO<T>( data class ApiResponseDTO<T>(
@SerializedName("success")
val success: Boolean = true, val success: Boolean = true,
@SerializedName("errors")
val errors: List<ErrorOrMessage> = listOf(), val errors: List<ErrorOrMessage> = listOf(),
@SerializedName("messages")
val messages: List<ErrorOrMessage> = listOf(), val messages: List<ErrorOrMessage> = listOf(),
@SerializedName("result")
val result: T? = null val result: T? = null
) { ) {
override fun toString(): String = gson.toJson(this) override fun toString(): String {
return gson.toJson(this)
}
} }
data class ErrorOrMessage( data class ErrorOrMessage(

View File

@ -17,42 +17,59 @@ class DomainService(
private val powerDNSApiClient: PowerDNSAPIClient private val powerDNSApiClient: PowerDNSAPIClient
) { ) {
fun getAllDomains(): List<Domain> { fun getAllDomains(): List<Domain> {
val user = getCurrentUser() try {
val domain = domainRepository.findAllByUser(user) val user = getCurrentUser()
if(domain.isEmpty()) throw RuntimeException("Unauthorized") val domain = domainRepository.findAllByUser(user)
if (domain.isEmpty()) throw RuntimeException("Unauthorized")
return domain return domain
} catch(ex: Error) {
throw ex
}
} }
fun getDomainById(domain_id: String): Domain { fun getDomainById(domain_id: String): Domain {
val domain = domainRepository.findByCfid(domain_id).orElseThrow { try {
RuntimeException("Failed to find domain in API: $domain_id") val domain = domainRepository.findByCfid(domain_id).orElseThrow {
} RuntimeException("Failed to find domain in API: $domain_id")
val user = getCurrentUser() }
if(domain.user.id != user.id) val user = getCurrentUser()
throw RuntimeException("Unauthorized to create record in API: $domain_id") if (domain.user.id != user.id)
throw RuntimeException("Unauthorized to create record in API: $domain_id")
return domain return domain
} catch(ex: Error) {
throw ex
}
} }
@Throws(Exception::class)
fun createDomain(domain: DomainRequestDTO): Domain { fun createDomain(domain: DomainRequestDTO): Domain {
val user = getCurrentUser() try {
val user = getCurrentUser()
powerDNSApiClient.createZone(domain.name) powerDNSApiClient.createZone(domain.name)
val saved_domain = domainRepository.save(Domain(name=domain.name, user=user)) val saved_domain = domainRepository.save(Domain(name = domain.name, user = user))
return saved_domain return saved_domain
} catch(ex: Error) {
throw ex
}
} }
fun deleteDomain(domain_id: String): String { fun deleteDomain(domain_id: String): String {
val domain = domainRepository.findByCfid(domain_id).orElseThrow { try {
throw RuntimeException("Domain with CFID $domain_id not found") val domain = domainRepository.findByCfid(domain_id).orElseThrow {
throw RuntimeException("Domain with CFID $domain_id not found")
}
powerDNSApiClient.deleteZone(domain.name)
val count = domainRepository.deleteByCfid(domain_id)
if (count > 0) throw RuntimeException("Domain with CFID $domain_id not found")
return domain_id
} catch(ex: Error) {
throw ex
} }
powerDNSApiClient.deleteZone(domain.name)
val count = domainRepository.deleteByCfid(domain_id)
if(count > 0) throw RuntimeException("Domain with CFID $domain_id not found")
return domain_id
} }
} }