org.octopusden.octopus.vcsfacade.service.impl.GiteaService.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of vcs-facade Show documentation
Show all versions of vcs-facade Show documentation
Octopus module: vcs-facade
The newest version!
package org.octopusden.octopus.vcsfacade.service.impl
import java.math.BigInteger
import java.net.URI
import java.net.URISyntaxException
import java.security.MessageDigest
import java.util.Date
import org.octopusden.octopus.infrastructure.client.commons.ClientParametersProvider
import org.octopusden.octopus.infrastructure.client.commons.CredentialProvider
import org.octopusden.octopus.infrastructure.client.commons.StandardBasicCredCredentialProvider
import org.octopusden.octopus.infrastructure.client.commons.StandardBearerTokenCredentialProvider
import org.octopusden.octopus.infrastructure.gitea.client.GiteaClassicClient
import org.octopusden.octopus.infrastructure.gitea.client.GiteaClient
import org.octopusden.octopus.infrastructure.gitea.client.createPullRequestWithDefaultReviewers
import org.octopusden.octopus.infrastructure.gitea.client.dto.GiteaBranch
import org.octopusden.octopus.infrastructure.gitea.client.dto.GiteaCommit
import org.octopusden.octopus.infrastructure.gitea.client.dto.GiteaCreateTag
import org.octopusden.octopus.infrastructure.gitea.client.dto.GiteaPullRequest
import org.octopusden.octopus.infrastructure.gitea.client.dto.GiteaPullRequestReview
import org.octopusden.octopus.infrastructure.gitea.client.dto.GiteaRepository
import org.octopusden.octopus.infrastructure.gitea.client.dto.GiteaTag
import org.octopusden.octopus.infrastructure.gitea.client.dto.GiteaUser
import org.octopusden.octopus.infrastructure.gitea.client.exception.NotFoundException
import org.octopusden.octopus.infrastructure.gitea.client.getBranches
import org.octopusden.octopus.infrastructure.gitea.client.getBranchesCommitGraph
import org.octopusden.octopus.infrastructure.gitea.client.getCommit
import org.octopusden.octopus.infrastructure.gitea.client.getCommits
import org.octopusden.octopus.infrastructure.gitea.client.getOrganizations
import org.octopusden.octopus.infrastructure.gitea.client.getPullRequestReviews
import org.octopusden.octopus.infrastructure.gitea.client.getPullRequests
import org.octopusden.octopus.infrastructure.gitea.client.getRepositories
import org.octopusden.octopus.infrastructure.gitea.client.getTags
import org.octopusden.octopus.vcsfacade.client.common.dto.Branch
import org.octopusden.octopus.vcsfacade.client.common.dto.Commit
import org.octopusden.octopus.vcsfacade.client.common.dto.CommitWithFiles
import org.octopusden.octopus.vcsfacade.client.common.dto.CreatePullRequest
import org.octopusden.octopus.vcsfacade.client.common.dto.CreateTag
import org.octopusden.octopus.vcsfacade.client.common.dto.FileChange
import org.octopusden.octopus.vcsfacade.client.common.dto.FileChangeType
import org.octopusden.octopus.vcsfacade.client.common.dto.PullRequest
import org.octopusden.octopus.vcsfacade.client.common.dto.PullRequestReviewer
import org.octopusden.octopus.vcsfacade.client.common.dto.PullRequestStatus
import org.octopusden.octopus.vcsfacade.client.common.dto.Repository
import org.octopusden.octopus.vcsfacade.client.common.dto.Tag
import org.octopusden.octopus.vcsfacade.client.common.dto.User
import org.octopusden.octopus.vcsfacade.config.VcsConfig
import org.octopusden.octopus.vcsfacade.dto.HashOrRefOrDate
import org.octopusden.octopus.vcsfacade.dto.HashOrRefOrDate.DateValue
import org.octopusden.octopus.vcsfacade.dto.HashOrRefOrDate.HashOrRefValue
import org.octopusden.octopus.vcsfacade.service.VcsService
import org.slf4j.LoggerFactory
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.stereotype.Service
@Service
@ConditionalOnProperty(
prefix = "vcs-facade.vcs.gitea", name = ["enabled"], havingValue = "true", matchIfMissing = true
)
class GiteaService(
giteaProperties: VcsConfig.GiteaProperties
) : VcsService(giteaProperties) {
private val client: GiteaClient = GiteaClassicClient(object : ClientParametersProvider {
override fun getApiUrl() = httpUrl
override fun getAuth(): CredentialProvider {
val authException by lazy {
IllegalStateException("Auth Token or username/password must be specified for Gitea access")
}
return giteaProperties.token?.let {
StandardBearerTokenCredentialProvider(it)
} ?: StandardBasicCredCredentialProvider(
giteaProperties.username ?: throw authException, giteaProperties.password ?: throw authException
)
}
})
override val sshUrlRegex = "(?:ssh://)?git@$host[:/]([^:/]+)/([^:/]+).git".toRegex()
fun getRepositories(): Sequence {
log.trace("=> getRepositories()")
return client.getOrganizations().asSequence().flatMap { client.getRepositories(it.name) }
.map { toRepository(it) }.also { log.trace("<= getRepositories(): {}", it) }
}
fun findRepository(group: String, repository: String): Repository? {
log.trace("=> findRepository({}, {})", group, repository)
return try {
toRepository(client.getRepository(group, repository))
} catch (e: NotFoundException) {
null
}.also { log.trace("<= findRepository({}, {}): {}", group, repository, it) }
}
fun getRepository(group: String, repository: String): Repository {
log.trace("=> getRepository({}, {})", group, repository)
return toRepository(client.getRepository(group, repository))
.also { log.trace("<= getRepository({}, {}): {}", group, repository, it) }
}
override fun getBranches(group: String, repository: String): Sequence {
log.trace("=> getBranches({}, {})", group, repository)
return with(getRepository(group, repository)) {
client.getBranches(group, repository).asSequence().map { it.toBranch(this) }
}.also { log.trace("<= getBranches({}, {}): {}", group, repository, it) }
}
override fun getTags(group: String, repository: String): Sequence {
log.trace("=> getTags({}, {})", group, repository)
return with(getRepository(group, repository)) {
client.getTags(group, repository).asSequence().map { it.toTag(this) }
}.also { log.trace("<= getTags({}, {}): {}", group, repository, it) }
}
override fun createTag(group: String, repository: String, createTag: CreateTag): Tag {
log.trace("=> createTag({}, {}, {})", group, repository, createTag)
return with(getRepository(group, repository)) {
client.createTag(
group, repository, GiteaCreateTag(createTag.name, createTag.hashOrRef, createTag.message)
).toTag(this)
}.also { log.trace("<= createTag({}, {}, {}): {}", group, repository, createTag, it) }
}
override fun getTag(group: String, repository: String, name: String): Tag {
log.trace("=> getTag({}, {}, {})", group, repository, name)
return with(getRepository(group, repository)) {
client.getTag(group, repository, name).toTag(this)
}.also { log.trace("<= getTag({}, {}, {}): {}", group, repository, name, it) }
}
override fun deleteTag(group: String, repository: String, name: String) {
log.trace("=> deleteTag({}, {}, {})", group, repository, name)
client.deleteTag(group, repository, name)
log.trace("<= deleteTag({}, {}, {})", group, repository, name)
}
override fun getCommits(
group: String, repository: String, from: HashOrRefOrDate?, toHashOrRef: String
): Sequence {
log.trace("=> getCommits({}, {}, {}, {})", group, repository, from, toHashOrRef)
return with(getRepository(group, repository)) {
val commits = if (from is HashOrRefValue) {
client.getCommits(group, repository, toHashOrRef, from.value)
} else {
client.getCommits(group, repository, toHashOrRef, (from as? DateValue)?.value)
}
commits.asSequence().map { it.toCommit(this) }
}.also { log.trace("<= getCommits({}, {}, {}, {}): {}", group, repository, from, toHashOrRef, it) }
}
override fun getCommitsWithFiles(
group: String, repository: String, from: HashOrRefOrDate?, toHashOrRef: String
): Sequence {
log.trace("=> getCommitsWithFiles({}, {}, {}, {})", group, repository, from, toHashOrRef)
return with(getRepository(group, repository)) {
val commits = if (from is HashOrRefValue) {
client.getCommits(group, repository, toHashOrRef, from.value, true)
} else {
client.getCommits(group, repository, toHashOrRef, (from as? DateValue)?.value, true)
}
commits.asSequence().map { it.toCommitWithFiles(this) }
}.also { log.trace("<= getCommitsWithFiles({}, {}, {}, {}): {}", group, repository, from, toHashOrRef, it) }
}
fun getBranchesCommitGraph(group: String, repository: String): Sequence {
log.trace("=> getBranchesCommitGraph({}, {})", group, repository)
return with(getRepository(group, repository)) {
client.getBranchesCommitGraph(group, repository, true).asSequence().map { it.toCommitWithFiles(this) }
}.also { log.trace("<= getBranchesCommitGraph({}, {}): {}", group, repository, it) }
}
override fun getCommit(group: String, repository: String, hashOrRef: String): Commit {
log.trace("=> getCommit({}, {}, {})", group, repository, hashOrRef)
return client.getCommit(group, repository, hashOrRef).toCommit(getRepository(group, repository))
.also { log.trace("<= getCommit({}, {}, {}): {}", group, repository, hashOrRef, it) }
}
override fun getCommitWithFiles(group: String, repository: String, hashOrRef: String): CommitWithFiles {
log.trace("=> getCommitWithFiles({}, {}, {})", group, repository, hashOrRef)
return client.getCommit(group, repository, hashOrRef, true).toCommitWithFiles(getRepository(group, repository))
.also { log.trace("<= getCommitWithFiles({}, {}, {}): {}", group, repository, hashOrRef, it) }
}
fun getPullRequestReviews(group: String, repository: String, number: Long): List {
log.trace("=> getPullRequestReviews({}, {}, {})", group, repository, number)
return client.getPullRequestReviews(group, repository, number)
.also { log.trace("<= getPullRequestReviews({}, {}, {}): {}", group, repository, number, it) }
}
fun getPullRequests(group: String, repository: String): Sequence {
log.trace("=> getPullRequests({}, {})", group, repository)
return with(getRepository(group, repository)) {
client.getPullRequests(group, repository).asSequence().map {
it.toPullRequest(this, getPullRequestReviews(group, repository, it.number))
}
}.also { log.trace("<= getPullRequests({}, {}): {}", group, repository, it) }
}
override fun createPullRequest(
group: String, repository: String, createPullRequest: CreatePullRequest
): PullRequest {
log.trace("=> getPullRequests({}, {}, {})", group, repository, createPullRequest)
return client.createPullRequestWithDefaultReviewers(
group,
repository,
createPullRequest.sourceBranch,
createPullRequest.targetBranch,
createPullRequest.title,
createPullRequest.description
).let {
it.toPullRequest(
getRepository(group, repository), client.getPullRequestReviews(group, repository, it.number)
)
}.also { log.trace("<= getPullRequests({}, {}, {}): {}", group, repository, createPullRequest, it) }
}
override fun getPullRequest(group: String, repository: String, index: Long): PullRequest {
log.trace("=> getPullRequest({}, {}, {})", group, repository, index)
return client.getPullRequest(group, repository, index).let {
it.toPullRequest(
getRepository(group, repository), client.getPullRequestReviews(group, repository, it.number)
)
}.also { log.trace("<= getPullRequest({}, {}, {}): {}", group, repository, index, it) }
}
override fun findCommits(group: String, repository: String, hashes: Set): Sequence {
log.trace("=> findCommits({}, {}, {})", group, repository, hashes)
return with(getRepository(group, repository)) {
hashes.mapNotNull {
try {
client.getCommit(group, repository, it).toCommit(this)
} catch (e: NotFoundException) {
null
}
}.asSequence()
}.also { log.trace("<= findCommits({}, {}, {}): {}", group, repository, hashes, it) }
}
override fun findPullRequests(group: String, repository: String, indexes: Set): Sequence {
log.trace("=> findPullRequests({}, {}, {})", group, repository, indexes)
return with(getRepository(group, repository)) {
indexes.mapNotNull {
try {
client.getPullRequest(group, repository, it)
.toPullRequest(this, client.getPullRequestReviews(group, repository, it))
} catch (e: NotFoundException) {
null
}
}.asSequence()
}.also { log.trace("<= findPullRequests({}, {}, {}): {}", group, repository, indexes, it) }
}
override fun findBranches(issueKey: String): Sequence {
log.warn("There is no native implementation of findBranches")
return emptySequence()
}
override fun findCommits(issueKey: String): Sequence {
log.warn("There is no native implementation of findCommits")
return emptySequence()
}
override fun findCommitsWithFiles(issueKey: String): Sequence {
log.warn("There is no native implementation of findCommitsWithFiles")
return emptySequence()
}
override fun findPullRequests(issueKey: String): Sequence {
log.warn("There is no native implementation of findPullRequests")
return emptySequence()
}
fun toRepository(giteaRepository: GiteaRepository): Repository {
val repository = giteaRepository.name.lowercase()
val organization = giteaRepository.fullName.lowercase().removeSuffix("/$repository")
return Repository("ssh://git@$host/$organization/$repository.git", //TODO: add "useColon" parameter?
"$httpUrl/$organization/$repository",
//IMPORTANT: see https://github.com/go-gitea/gitea/pull/31187
//Gitea versions 1.22.0 and 1.22.1 return host url instead of empty string as avatar_url for repository with no avatar
giteaRepository.avatarUrl.let {
val path = try {
URI(it).path
} catch (e: URISyntaxException) {
""
}
if (path.trim('/').isEmpty()) null else it
}
//TODO: restore `giteaRepository.avatarUrl.ifBlank { null }` after Gitea fix
)
}
companion object {
private val log = LoggerFactory.getLogger(GiteaService::class.java)
fun GiteaBranch.toBranch(repository: Repository) = Branch(
name, commit.id, "${repository.link}/src/branch/$name", repository
)
fun GiteaTag.toTag(repository: Repository) = Tag(
name, commit.sha, "${repository.link}/src/tag/$name", repository
)
private fun GiteaUser.toUser() = User(username, avatarUrl.ifBlank { null })
private fun GiteaCommit.toCommit(repository: Repository) = Commit(
sha,
commit.message,
created,
author?.toUser() ?: User(commit.author.name),
parents.map { it.sha },
"${repository.link}/commit/$sha",
repository
)
private fun String.sha1() =
BigInteger(1, MessageDigest.getInstance("SHA-1").digest(toByteArray())).toString(16).padStart(32, '0')
private fun GiteaCommit.toCommitWithFiles(repository: Repository) = with(toCommit(repository)) {
val fileChanges = files!!.map {
FileChange(
when (it.status) {
GiteaCommit.GiteaCommitAffectedFileStatus.ADDED -> FileChangeType.ADD
GiteaCommit.GiteaCommitAffectedFileStatus.MODIFIED -> FileChangeType.MODIFY
GiteaCommit.GiteaCommitAffectedFileStatus.REMOVED -> FileChangeType.DELETE
}, it.filename, "${this.link}#diff-${it.filename.sha1()}"
)
}
CommitWithFiles(this, fileChanges.size, fileChanges)
}
fun GiteaPullRequest.toPullRequest(
repository: Repository, giteaPullRequestReviews: List
): PullRequest {
val approvedGiteaUserIds = giteaPullRequestReviews.filter {
it.state == GiteaPullRequestReview.GiteaPullRequestReviewState.APPROVED && !it.dismissed
}.mapNotNull { it.user?.id }.toSet()
return PullRequest(
number,
title,
body,
user.toUser(),
head.label,
base.label,
assignees.map { it.toUser() },
requestedReviewers.map { PullRequestReviewer(it.toUser(), approvedGiteaUserIds.contains(it.id)) },
if (merged) {
PullRequestStatus.MERGED
} else if (state == GiteaPullRequest.GiteaPullRequestState.CLOSED) {
PullRequestStatus.DECLINED
} else {
PullRequestStatus.OPEN
},
createdAt,
updatedAt,
"${repository.link}/pulls/$number",
repository
)
}
}
}