fix pdns api clients (retry 5x)

This commit is contained in:
dalbodeule 2024-06-07 00:38:42 +09:00
parent 4a72cd0453
commit a41d8ae23c
No known key found for this signature in database
GPG Key ID: EFA860D069C9FA65
4 changed files with 85 additions and 76 deletions

View File

@ -42,6 +42,9 @@ dependencies {
implementation("com.google.code.gson:gson:2.11.0") implementation("com.google.code.gson:gson:2.11.0")
implementation("io.github.cdimascio:dotenv-kotlin:6.4.1") 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") runtimeOnly("org.mariadb.jdbc:mariadb-java-client")
testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.springframework.boot:spring-boot-starter-test")

View File

@ -1,14 +1,15 @@
package space.mori.dnsapi package space.mori.dnsapi
import com.google.gson.Gson 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.beans.factory.annotation.Value
import org.springframework.http.*
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import org.springframework.web.client.RestTemplate
import space.mori.dnsapi.dto.RecordRequestDTO
@Service @Service
class PowerDNSApiClient { class PowerDNSAPIClient() {
@Value("\${pdns.api.url}") @Value("\${pdns.api.url}")
private lateinit var apiUrl: String private lateinit var apiUrl: String
@ -18,68 +19,73 @@ class PowerDNSApiClient {
@Value("\${pdns.ns}") @Value("\${pdns.ns}")
private lateinit var nameserver: String private lateinit var nameserver: String
private val restTemplate = RestTemplate()
private val gson = Gson() private val gson = Gson()
private val client = OkHttpClient()
private fun createHeaders(): HttpHeaders { fun createZone(zoneName: String): Boolean {
val headers = HttpHeaders() val body = gson.toJson(mapOf(
headers.set("X-API-Key", apiKey) "name" to zoneName,
headers.contentType = MediaType.APPLICATION_JSON "nameservers" to nameserver.split(","))
return headers ).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<String> { fun deleteZone(zoneName: String): Boolean {
val url = "$apiUrl/api/v1/servers/localhost/zones" val request = Request.Builder()
val headers = createHeaders() .url("$apiUrl/api/v1/servers/localhost/zones/$zoneName")
val domainRequest = DomainRequest("$name.", "Native", arrayOf(), nameserver.split(",").toTypedArray()) .addHeader("Authorization", "Bearer $apiKey")
val body = gson.toJson(domainRequest) .delete()
val entity = HttpEntity(body, headers) .build()
return restTemplate.exchange(url, HttpMethod.POST, entity, String::class.java)
val response = client.newCall(request).execute()
return response.isSuccessful
} }
fun createRecord(domainName: String, recordRequest: RecordRequestDTO): ResponseEntity<String> { fun createRecord(zoneName: String, recordName: String, recordType: String, recordContent: String): Boolean {
val url = "$apiUrl/api/v1/servers/localhost/zones/$domainName." val body = gson.toJson(mapOf(
val headers = createHeaders() "name" to recordName,
val record = RecordRequest( "type" to recordType,
name = "${recordRequest.name}.$domainName.", "content" to recordContent
type = recordRequest.type, )).toRequestBody("application/json; charset=utf-8".toMediaType())
ttl = recordRequest.ttl, val request = Request.Builder()
changetype = "REPLACE", .url("$apiUrl/api/v1/servers/localhost/zones/$zoneName/records")
records = arrayOf(RecordContent(recordRequest.content, false)) .addHeader("Authorization", "Bearer $apiKey")
) .post(body)
val body = gson.toJson(RecordRequestWrapper(arrayOf(record))) .build()
val entity = HttpEntity(body, headers)
return restTemplate.exchange(url, HttpMethod.PATCH, entity, String::class.java) val response = client.newCall(request).execute()
return response.isSuccessful
} }
fun deleteDomain(name: String): ResponseEntity<String> { fun updateRecord(zoneName: String, recordName: String, recordType: String, recordContent: String): Boolean {
val url = "$apiUrl/api/v1/servers/localhost/zones/$name." val body = gson.toJson(mapOf(
val headers = createHeaders() "content" to recordContent
val entity = HttpEntity<String>(headers) )).toRequestBody("application/json; charset=utf-8".toMediaType())
return restTemplate.exchange(url, HttpMethod.DELETE, entity, String::class.java) 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( fun deleteRecord(zoneName: String, recordName: String, recordType: String): Boolean {
val name: String, val request = Request.Builder()
val kind: String, .url("$apiUrl/api/v1/servers/localhost/zones/$zoneName/records/$recordName/$recordType")
val masters: Array<String>, .addHeader("Authorization", "Bearer $apiKey")
val nameservers: Array<String> .delete()
) .build()
private data class RecordRequestWrapper( val response = client.newCall(request).execute()
val rrsets: Array<RecordRequest> return response.isSuccessful
) }
private data class RecordRequest(
val name: String,
val type: String,
val ttl: Int,
val changetype: String,
val records: Array<RecordContent>
)
private data class RecordContent(
val content: String,
val disabled: Boolean
)
} }

View File

@ -2,7 +2,7 @@ package space.mori.dnsapi.service
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service 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.Domain
import space.mori.dnsapi.db.DomainRepository import space.mori.dnsapi.db.DomainRepository
import space.mori.dnsapi.dto.DomainRequestDTO import space.mori.dnsapi.dto.DomainRequestDTO
@ -14,7 +14,7 @@ class DomainService(
@Autowired @Autowired
private val domainRepository: DomainRepository, private val domainRepository: DomainRepository,
@Autowired @Autowired
private val powerDNSApiClient: PowerDNSApiClient private val powerDNSApiClient: PowerDNSAPIClient
) { ) {
fun getAllDomains(): List<Domain> { fun getAllDomains(): List<Domain> {
val user = getCurrentUser() val user = getCurrentUser()
@ -38,7 +38,7 @@ class DomainService(
fun createDomain(domain: DomainRequestDTO): Domain { fun createDomain(domain: DomainRequestDTO): Domain {
val user = getCurrentUser() val user = getCurrentUser()
powerDNSApiClient.createDomain(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
@ -49,7 +49,7 @@ class DomainService(
throw RuntimeException("Domain with CFID $domain_id not found") throw RuntimeException("Domain with CFID $domain_id not found")
} }
powerDNSApiClient.deleteDomain(domain.name) powerDNSApiClient.deleteZone(domain.name)
val count = domainRepository.deleteByCfid(domain_id) val count = domainRepository.deleteByCfid(domain_id)
if(count > 0) throw RuntimeException("Domain with CFID $domain_id not found") if(count > 0) throw RuntimeException("Domain with CFID $domain_id not found")

View File

@ -3,12 +3,10 @@ package space.mori.dnsapi.service
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional 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.DomainRepository
import space.mori.dnsapi.db.Record as DomainRecord import space.mori.dnsapi.db.Record as DomainRecord
import space.mori.dnsapi.db.RecordRepository 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.RecordRequestDTO
import space.mori.dnsapi.dto.RecordResponseDTO import space.mori.dnsapi.dto.RecordResponseDTO
import space.mori.dnsapi.filter.getCurrentUser import space.mori.dnsapi.filter.getCurrentUser
@ -19,13 +17,11 @@ import java.util.*
@Service @Service
class RecordService( class RecordService(
@Autowired @Autowired
private val powerDNSApiClient: PowerDNSApiClient, private val powerDNSApiClient: PowerDNSAPIClient,
@Autowired @Autowired
private val domainRepository: DomainRepository, private val domainRepository: DomainRepository,
@Autowired @Autowired
private val recordRepository: RecordRepository, private val recordRepository: RecordRepository,
@Autowired
private val userRepository: UserRepository,
) { ) {
fun createRecord(domain_id: String, recordRequest: RecordRequestDTO): RecordResponseDTO { fun createRecord(domain_id: String, recordRequest: RecordRequestDTO): RecordResponseDTO {
val domain = domainRepository.findByCfid(domain_id).orElseThrow { val domain = domainRepository.findByCfid(domain_id).orElseThrow {
@ -36,9 +32,9 @@ class RecordService(
if(domain.user.id != user.id) if(domain.user.id != user.id)
throw RuntimeException("Unauthorized to create record in API: $domain_id") throw RuntimeException("Unauthorized to create record in API: $domain_id")
val response = powerDNSApiClient.createRecord(domain.name, recordRequest) val response = powerDNSApiClient.createRecord(domain.name, recordRequest.name, recordRequest.type, recordRequest.content)
if (!response.statusCode.is2xxSuccessful) { if (!response) {
throw RuntimeException("Failed to create record in PowerDNS: ${response.body}") throw RuntimeException("Failed to create record in PowerDNS")
} }
val record = DomainRecord( val record = DomainRecord(
domain = domain, domain = domain,
@ -146,9 +142,9 @@ class RecordService(
record.comment = updatedRecord.comment record.comment = updatedRecord.comment
record.modifiedOn = Date() record.modifiedOn = Date()
val response = powerDNSApiClient.createRecord(domain!!.name, updatedRecord) val response = powerDNSApiClient.updateRecord(domain!!.name, updatedRecord.name, updatedRecord.type, updatedRecord.content)
if (!response.statusCode.is2xxSuccessful) { if (!response) {
throw RuntimeException("Failed to update record in PowerDNS: ${response.body}") throw RuntimeException("Failed to update record in PowerDNS")
} }
// 저장 // 저장
@ -179,9 +175,13 @@ class RecordService(
if(domain.user.id != user.id) if(domain.user.id != user.id)
throw RuntimeException("Unauthorized to create record in API: $domain_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") powerDNSApiClient.deleteRecord(domain.name, record.name, record.type)
else return record_id recordRepository.deleteByDomainIdAndCfid(domain.id!!, record_id)
return record_id
} }
} }