From a41d8ae23c23bcdefc316f39a22aeafdbaf3be54 Mon Sep 17 00:00:00 2001 From: dalbodeule <11470513+dalbodeule@users.noreply.github.com> Date: Fri, 7 Jun 2024 00:38:42 +0900 Subject: [PATCH] fix pdns api clients (retry 5x) --- build.gradle.kts | 3 + .../space/mori/dnsapi/PowerDNSAPIClient.kt | 120 +++++++++--------- .../mori/dnsapi/service/DomainService.kt | 8 +- .../mori/dnsapi/service/RecordService.kt | 30 ++--- 4 files changed, 85 insertions(+), 76 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index e4a60f8..92b27d1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -42,6 +42,9 @@ dependencies { implementation("com.google.code.gson:gson:2.11.0") implementation("io.github.cdimascio:dotenv-kotlin:6.4.1") + // https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp + implementation("com.squareup.okhttp3:okhttp:4.12.0") + runtimeOnly("org.mariadb.jdbc:mariadb-java-client") testImplementation("org.springframework.boot:spring-boot-starter-test") diff --git a/src/main/kotlin/space/mori/dnsapi/PowerDNSAPIClient.kt b/src/main/kotlin/space/mori/dnsapi/PowerDNSAPIClient.kt index c022d24..5082223 100644 --- a/src/main/kotlin/space/mori/dnsapi/PowerDNSAPIClient.kt +++ b/src/main/kotlin/space/mori/dnsapi/PowerDNSAPIClient.kt @@ -1,14 +1,15 @@ package space.mori.dnsapi import com.google.gson.Gson +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody import org.springframework.beans.factory.annotation.Value -import org.springframework.http.* import org.springframework.stereotype.Service -import org.springframework.web.client.RestTemplate -import space.mori.dnsapi.dto.RecordRequestDTO @Service -class PowerDNSApiClient { +class PowerDNSAPIClient() { @Value("\${pdns.api.url}") private lateinit var apiUrl: String @@ -18,68 +19,73 @@ class PowerDNSApiClient { @Value("\${pdns.ns}") private lateinit var nameserver: String - private val restTemplate = RestTemplate() private val gson = Gson() + private val client = OkHttpClient() - private fun createHeaders(): HttpHeaders { - val headers = HttpHeaders() - headers.set("X-API-Key", apiKey) - headers.contentType = MediaType.APPLICATION_JSON - return headers + fun createZone(zoneName: String): Boolean { + val body = gson.toJson(mapOf( + "name" to zoneName, + "nameservers" to nameserver.split(",")) + ).toRequestBody("application/json; charset=utf-8".toMediaType()) + val request = Request.Builder() + .url("$apiUrl/api/v1/servers/localhost/zones") + .addHeader("Authorization", "Bearer $apiKey") + .post(body) + .build() + + val response = client.newCall(request).execute() + return response.isSuccessful } - fun createDomain(name: String): ResponseEntity { - val url = "$apiUrl/api/v1/servers/localhost/zones" - val headers = createHeaders() - val domainRequest = DomainRequest("$name.", "Native", arrayOf(), nameserver.split(",").toTypedArray()) - val body = gson.toJson(domainRequest) - val entity = HttpEntity(body, headers) - return restTemplate.exchange(url, HttpMethod.POST, entity, String::class.java) + fun deleteZone(zoneName: String): Boolean { + val request = Request.Builder() + .url("$apiUrl/api/v1/servers/localhost/zones/$zoneName") + .addHeader("Authorization", "Bearer $apiKey") + .delete() + .build() + + val response = client.newCall(request).execute() + return response.isSuccessful } - fun createRecord(domainName: String, recordRequest: RecordRequestDTO): ResponseEntity { - val url = "$apiUrl/api/v1/servers/localhost/zones/$domainName." - val headers = createHeaders() - val record = RecordRequest( - name = "${recordRequest.name}.$domainName.", - type = recordRequest.type, - ttl = recordRequest.ttl, - changetype = "REPLACE", - records = arrayOf(RecordContent(recordRequest.content, false)) - ) - val body = gson.toJson(RecordRequestWrapper(arrayOf(record))) - val entity = HttpEntity(body, headers) - return restTemplate.exchange(url, HttpMethod.PATCH, entity, String::class.java) + fun createRecord(zoneName: String, recordName: String, recordType: String, recordContent: String): Boolean { + val body = gson.toJson(mapOf( + "name" to recordName, + "type" to recordType, + "content" to recordContent + )).toRequestBody("application/json; charset=utf-8".toMediaType()) + val request = Request.Builder() + .url("$apiUrl/api/v1/servers/localhost/zones/$zoneName/records") + .addHeader("Authorization", "Bearer $apiKey") + .post(body) + .build() + + val response = client.newCall(request).execute() + return response.isSuccessful } - fun deleteDomain(name: String): ResponseEntity { - val url = "$apiUrl/api/v1/servers/localhost/zones/$name." - val headers = createHeaders() - val entity = HttpEntity(headers) - return restTemplate.exchange(url, HttpMethod.DELETE, entity, String::class.java) + fun updateRecord(zoneName: String, recordName: String, recordType: String, recordContent: String): Boolean { + val body = gson.toJson(mapOf( + "content" to recordContent + )).toRequestBody("application/json; charset=utf-8".toMediaType()) + val request = Request.Builder() + .url("$apiUrl/api/v1/servers/localhost/zones/$zoneName/records/$recordName/$recordType") + .addHeader("Authorization", "Bearer $apiKey") + .put(body) + .build() + + val response = client.newCall(request).execute() + return response.isSuccessful } - private data class DomainRequest( - val name: String, - val kind: String, - val masters: Array, - val nameservers: Array - ) + fun deleteRecord(zoneName: String, recordName: String, recordType: String): Boolean { + val request = Request.Builder() + .url("$apiUrl/api/v1/servers/localhost/zones/$zoneName/records/$recordName/$recordType") + .addHeader("Authorization", "Bearer $apiKey") + .delete() + .build() - private data class RecordRequestWrapper( - val rrsets: Array - ) - - private data class RecordRequest( - val name: String, - val type: String, - val ttl: Int, - val changetype: String, - val records: Array - ) - - private data class RecordContent( - val content: String, - val disabled: Boolean - ) + val response = client.newCall(request).execute() + return response.isSuccessful + } } \ No newline at end of file diff --git a/src/main/kotlin/space/mori/dnsapi/service/DomainService.kt b/src/main/kotlin/space/mori/dnsapi/service/DomainService.kt index 57cd015..e14854f 100644 --- a/src/main/kotlin/space/mori/dnsapi/service/DomainService.kt +++ b/src/main/kotlin/space/mori/dnsapi/service/DomainService.kt @@ -2,7 +2,7 @@ package space.mori.dnsapi.service import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service -import space.mori.dnsapi.PowerDNSApiClient +import space.mori.dnsapi.PowerDNSAPIClient import space.mori.dnsapi.db.Domain import space.mori.dnsapi.db.DomainRepository import space.mori.dnsapi.dto.DomainRequestDTO @@ -14,7 +14,7 @@ class DomainService( @Autowired private val domainRepository: DomainRepository, @Autowired - private val powerDNSApiClient: PowerDNSApiClient + private val powerDNSApiClient: PowerDNSAPIClient ) { fun getAllDomains(): List { val user = getCurrentUser() @@ -38,7 +38,7 @@ class DomainService( fun createDomain(domain: DomainRequestDTO): Domain { val user = getCurrentUser() - powerDNSApiClient.createDomain(domain.name) + powerDNSApiClient.createZone(domain.name) val saved_domain = domainRepository.save(Domain(name=domain.name, user=user)) return saved_domain @@ -49,7 +49,7 @@ class DomainService( throw RuntimeException("Domain with CFID $domain_id not found") } - powerDNSApiClient.deleteDomain(domain.name) + powerDNSApiClient.deleteZone(domain.name) val count = domainRepository.deleteByCfid(domain_id) if(count > 0) throw RuntimeException("Domain with CFID $domain_id not found") diff --git a/src/main/kotlin/space/mori/dnsapi/service/RecordService.kt b/src/main/kotlin/space/mori/dnsapi/service/RecordService.kt index 2aadd3a..c2c534e 100644 --- a/src/main/kotlin/space/mori/dnsapi/service/RecordService.kt +++ b/src/main/kotlin/space/mori/dnsapi/service/RecordService.kt @@ -3,12 +3,10 @@ package space.mori.dnsapi.service import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional -import space.mori.dnsapi.PowerDNSApiClient +import space.mori.dnsapi.PowerDNSAPIClient import space.mori.dnsapi.db.DomainRepository import space.mori.dnsapi.db.Record as DomainRecord import space.mori.dnsapi.db.RecordRepository -import space.mori.dnsapi.db.UserRepository -import space.mori.dnsapi.dto.DomainRequestDTO import space.mori.dnsapi.dto.RecordRequestDTO import space.mori.dnsapi.dto.RecordResponseDTO import space.mori.dnsapi.filter.getCurrentUser @@ -19,13 +17,11 @@ import java.util.* @Service class RecordService( @Autowired - private val powerDNSApiClient: PowerDNSApiClient, + private val powerDNSApiClient: PowerDNSAPIClient, @Autowired private val domainRepository: DomainRepository, @Autowired private val recordRepository: RecordRepository, - @Autowired - private val userRepository: UserRepository, ) { fun createRecord(domain_id: String, recordRequest: RecordRequestDTO): RecordResponseDTO { val domain = domainRepository.findByCfid(domain_id).orElseThrow { @@ -36,9 +32,9 @@ class RecordService( if(domain.user.id != user.id) throw RuntimeException("Unauthorized to create record in API: $domain_id") - val response = powerDNSApiClient.createRecord(domain.name, recordRequest) - if (!response.statusCode.is2xxSuccessful) { - throw RuntimeException("Failed to create record in PowerDNS: ${response.body}") + val response = powerDNSApiClient.createRecord(domain.name, recordRequest.name, recordRequest.type, recordRequest.content) + if (!response) { + throw RuntimeException("Failed to create record in PowerDNS") } val record = DomainRecord( domain = domain, @@ -146,9 +142,9 @@ class RecordService( record.comment = updatedRecord.comment record.modifiedOn = Date() - val response = powerDNSApiClient.createRecord(domain!!.name, updatedRecord) - if (!response.statusCode.is2xxSuccessful) { - throw RuntimeException("Failed to update record in PowerDNS: ${response.body}") + val response = powerDNSApiClient.updateRecord(domain!!.name, updatedRecord.name, updatedRecord.type, updatedRecord.content) + if (!response) { + throw RuntimeException("Failed to update record in PowerDNS") } // 저장 @@ -179,9 +175,13 @@ class RecordService( if(domain.user.id != user.id) throw RuntimeException("Unauthorized to create record in API: $domain_id") - val deletedCount = recordRepository.deleteByDomainIdAndCfid(domain.id!!, record_id) + val record = recordRepository.findByDomainIdAndCfid(domain.id!!, record_id).orElseThrow { + RuntimeException("Failed to find record in API: $record_id") + } - if(deletedCount == 0) throw RuntimeException("Failed to find record in API: $record_id") - else return record_id + powerDNSApiClient.deleteRecord(domain.name, record.name, record.type) + recordRepository.deleteByDomainIdAndCfid(domain.id!!, record_id) + + return record_id } } \ No newline at end of file