Skip to content

Commit

Permalink
octopus-vcs-facade-12 Add token refresh for Gitlab basic auth && retr…
Browse files Browse the repository at this point in the history
…yable request (#15)
  • Loading branch information
aryabokon authored Sep 4, 2023
1 parent 7628a3e commit 3c970f3
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 55 deletions.
14 changes: 9 additions & 5 deletions .run/FT.run.xml
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="FT" type="GradleRunConfiguration" factoryName="Gradle">
<configuration default="false" name="FT" type="GradleRunConfiguration" factoryName="Gradle" folderName="dev">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="clean build ft --info" />
<option name="scriptParameters" value="--info" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list />
<list>
<option value="clean" />
<option value="build" />
<option value="ft" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<EXTENSION ID="com.intellij.execution.ExternalSystemRunConfigurationJavaExtension">
<extension name="net.ashald.envfile">
<option name="IS_ENABLED" value="false" />
<option name="IS_ENABLED" value="true" />
<option name="IS_SUBST" value="true" />
<option name="IS_PATH_MACRO_SUPPORTED" value="true" />
<option name="IS_IGNORE_MISSING_FILES" value="true" />
Expand All @@ -31,4 +35,4 @@
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2" />
</configuration>
</component>
</component>
12 changes: 7 additions & 5 deletions .run/UT.run.xml
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="UT" type="GradleRunConfiguration" factoryName="Gradle">
<configuration default="false" name="UT" type="GradleRunConfiguration" factoryName="Gradle" folderName="dev">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value=":vcs-facade:test --info" />
<option name="scriptParameters" value="--info" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list />
<list>
<option value=":vcs-facade:test" />
</list>
</option>
<option name="vmOptions" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<EXTENSION ID="com.intellij.execution.ExternalSystemRunConfigurationJavaExtension">
<extension name="net.ashald.envfile">
<option name="IS_ENABLED" value="false" />
<option name="IS_ENABLED" value="true" />
<option name="IS_SUBST" value="true" />
<option name="IS_PATH_MACRO_SUPPORTED" value="true" />
<option name="IS_IGNORE_MISSING_FILES" value="true" />
Expand All @@ -31,4 +33,4 @@
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2" />
</configuration>
</component>
</component>
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ spring-boot.version=2.7.3
spring-cloud.version=2021.0.3
kotlin.version=1.6.21
junit-jupiter.version=5.3.2
external-systems-client.version=2.0.12
external-systems-client.version=2.0.13

docker.registry=
octopus.github.docker.registry=
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,33 @@ import org.octopusden.octopus.vcsfacade.service.VCSClient
import org.slf4j.LoggerFactory
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.stereotype.Service
import java.time.Duration
import java.time.Instant
import java.util.Date
import java.util.Stack
import java.util.concurrent.TimeUnit

@Service
@ConditionalOnProperty(prefix = "gitlab", name = ["enabled"], havingValue = "true", matchIfMissing = true)
class GitlabService(gitLabProperties: VCSConfig.GitLabProperties) : VCSClient(gitLabProperties) {
class GitlabServiceImpl(gitLabProperties: VCSConfig.GitLabProperties) : VCSClient(gitLabProperties) {

override val repoPrefix: String = "git@"

private val gitlabApiFunc: () -> GitLabApi = {
private val clientFunc: () -> GitLabApi = {
val authException by lazy {
IllegalStateException("Auth Token or username/password must be specified for Bitbucket access")
}
gitLabProperties.token
?.let { GitLabApi(gitLabProperties.host, gitLabProperties.token) }
?: GitLabApi.oauth2Login(
gitLabProperties.host,
gitLabProperties.username ?: throw authException,
gitLabProperties.password ?: throw authException
)
?: getGitlabApi(gitLabProperties, authException).also { api ->
api.setAuthTokenSupplier { getToken(gitLabProperties, authException) }
}
}

private val gitLabApi by lazy { gitlabApiFunc() }
private val client by lazy { clientFunc() }

private var tokenObtained: Instant = Instant.MIN
private var token: String = ""

/**
* fromId and fromDate are not works together, must be specified one of it or not one
Expand All @@ -46,12 +50,14 @@ class GitlabService(gitLabProperties: VCSConfig.GitLabProperties) : VCSClient(gi
val toIdValue = getBranchCommit(vcsPath, toId)?.id ?: toId
getCommit(vcsPath, toIdValue)

val commits = gitLabApi.commitsApi
.getCommits(project.id, toIdValue, null, null, 100)
.asSequence()
.flatten()
.map { commit -> Commit(commit.id, commit.message, commit.committedDate, commit.authorName, commit.parentIds, vcsPath) }
.toList()
val commits = retryableExecution {
client.commitsApi
.getCommits(project.id, toIdValue, null, null, 100)
.asSequence()
.flatten()
.map { c -> Commit(c.id, c.message, c.committedDate, c.authorName, c.parentIds, vcsPath) }
.toList()
}

val graph = buildGraph(commits)
if (log.isTraceEnabled) {
Expand Down Expand Up @@ -97,16 +103,19 @@ class GitlabService(gitLabProperties: VCSConfig.GitLabProperties) : VCSClient(gi

override fun getTags(vcsPath: String): List<Tag> {
val project = getProject(vcsPath)
return gitLabApi.tagsApi.getTags(project.id)
return retryableExecution {
client.tagsApi
.getTags(project.id)
.asSequence()
.map { Tag(it.commit.id, it.name) }
.toList()
}
}

override fun getCommit(vcsPath: String, commitId: String): Commit {
val project = getProject(vcsPath)
return execute("Commit '$commitId' does not exist in repository '${project.name}'.") {
val commit = gitLabApi.commitsApi
return retryableExecution("Commit '$commitId' does not exist in repository '${project.name}'.") {
val commit = client.commitsApi
.getCommit(project.id, commitId)
Commit(commit.id, commit.message, commit.committedDate, commit.authorName, commit.parentIds, vcsPath)
}
Expand All @@ -121,42 +130,36 @@ class GitlabService(gitLabProperties: VCSConfig.GitLabProperties) : VCSClient(gi
val sourceBranch = pullRequestRequest.sourceBranch.toShortBranchName()
val targetBranch = pullRequestRequest.targetBranch.toShortBranchName()

execute("Source branch 'absent' not found in '$namespace:$projectName'") { gitLabApi.repositoryApi.getBranch(project.id, sourceBranch) }
execute("Target branch 'absent' not found in '$namespace:$projectName'") { gitLabApi.repositoryApi.getBranch(project.id, targetBranch) }

val mergeRequest = gitLabApi.mergeRequestApi.createMergeRequest(
project.id,
sourceBranch,
targetBranch,
pullRequestRequest.title,
pullRequestRequest.description,
0L
)
retryableExecution("Source branch 'absent' not found in '$namespace:$projectName'") { client.repositoryApi.getBranch(project.id, sourceBranch) }
retryableExecution("Target branch 'absent' not found in '$namespace:$projectName'") { client.repositoryApi.getBranch(project.id, targetBranch) }

val mergeRequest = retryableExecution {
client.mergeRequestApi.createMergeRequest(
project.id,
sourceBranch,
targetBranch,
pullRequestRequest.title,
pullRequestRequest.description,
0L
)
}
return PullRequestResponse(mergeRequest.id)
}

private fun getBranchCommit(vcsPath: String, branch: String) : org.gitlab4j.api.models.Commit? {
val shortBranchName = branch.toShortBranchName()
val project = getProject(vcsPath)
return gitLabApi.repositoryApi.getBranches(project, shortBranchName).firstOrNull { b -> b.name == shortBranchName }?.commit
return retryableExecution {
client.repositoryApi.getBranches(project, shortBranchName)
.firstOrNull { b -> b.name == shortBranchName }?.commit
}
}

private fun getProject(vcsPath: String): Project {
val (namespace, project) = vcsPath.toNamespaceAndProject()
execute("Project $namespace does not exist.") { gitLabApi.groupApi.getGroup(namespace) }
return execute("Repository $namespace/$project does not exist.") {
gitLabApi.projectApi.getProject(namespace, project)
}
}

private fun <T> execute(message: String, func: () -> T ): T {
try {
return func()
} catch (e: GitLabApiException) {
if (e.httpStatus == 404) {
throw NotFoundException(message)
}
throw IllegalStateException(e.message)
retryableExecution("Project $namespace does not exist.") { client.groupApi.getGroup(namespace) }
return retryableExecution("Repository $namespace/$project does not exist.") {
client.projectApi.getProject(namespace, project)
}
}

Expand All @@ -176,10 +179,56 @@ class GitlabService(gitLabProperties: VCSConfig.GitLabProperties) : VCSClient(gi
}
return visited
}

private fun String.toShortBranchName() = this.replace("^refs/heads/".toRegex(), "")

private fun <T> retryableExecution(
message: String = "",
attemptLimit: Int = 3,
attemptIntervalSec: Long = 3,
func: () -> T
): T {
lateinit var latestException: Exception
for (attempt in 1..attemptLimit) {
try {
return func()
} catch (e: GitLabApiException) {
if (e.httpStatus == 404) {
throw NotFoundException(message)
}
log.error("${e.message}, attempt=$attempt:$attemptLimit, retry in $attemptIntervalSec sec")
latestException = e
TimeUnit.SECONDS.sleep(attemptIntervalSec)
}
}
throw IllegalStateException(latestException.message)
}

private fun getGitlabApi(
gitLabProperties: VCSConfig.GitLabProperties,
authException: IllegalStateException
) = GitLabApi.oauth2Login(
gitLabProperties.host,
gitLabProperties.username ?: throw authException,
gitLabProperties.password ?: throw authException
).also { api ->
tokenObtained = Instant.now()
token = api.authToken
}

private fun getToken(
gitLabProperties: VCSConfig.GitLabProperties,
authException: IllegalStateException
): String {
if (tokenObtained.isBefore(Instant.now().minus(Duration.ofMinutes(110)))) {
log.info("Refresh auth token")
getGitlabApi(gitLabProperties, authException)
}
return token
}

companion object {
private val log = LoggerFactory.getLogger(GitlabService::class.java)
private val log = LoggerFactory.getLogger(GitlabServiceImpl::class.java)
}
}

Expand Down

0 comments on commit 3c970f3

Please sign in to comment.