Skip to content

Commit

Permalink
Merge pull request #34 from androidx/betterretries
Browse files Browse the repository at this point in the history
Retry reading default GCP credentials in the expired case
  • Loading branch information
liutikas authored Jul 26, 2023
2 parents 8f20c58 + 7d69a41 commit 9ac2daf
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 24 deletions.
2 changes: 1 addition & 1 deletion gcpbuildcache/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ gradlePlugin {
}

group = "androidx.build.gradle.gcpbuildcache"
version = "1.0.0-beta03"
version = "1.0.0-beta04"

testing {
suites {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,8 @@ abstract class GcpBuildCache : RemoteGradleBuildCache() {
* The type of credentials to use to connect to the Google Cloud Platform project instance.
*/
override var credentials: GcpCredentials = ApplicationDefaultGcpCredentials

var messageOnAuthenticationFailure: String = """Your GCP Credentials have expired.
Please regenerate credentials following the steps below and try again:
gcloud auth application-default login""".trimIndent()
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ internal class GcpBuildCacheService(
private val projectId: String,
private val bucketName: String,
gcpCredentials: GcpCredentials,
messageOnAuthenticationFailure: String,
isPush: Boolean,
isEnabled: Boolean,
inTestMode: Boolean = false
Expand All @@ -47,7 +48,7 @@ internal class GcpBuildCacheService(
// Use an implementation backed by the File System when in test mode.
FileSystemStorageService(bucketName, isPush, isEnabled)
} else {
GcpStorageService(projectId, bucketName, gcpCredentials, isPush, isEnabled)
GcpStorageService(projectId, bucketName, gcpCredentials, messageOnAuthenticationFailure, isPush, isEnabled)
}

override fun close() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class GcpBuildCacheServiceFactory : BuildCacheServiceFactory<GcpBuildCache> {
buildCache.projectId,
buildCache.bucketName,
buildCache.credentials,
buildCache.messageOnAuthenticationFailure,
buildCache.isPush,
buildCache.isEnabled
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,15 @@ internal class GcpStorageService(
private val projectId: String,
override val bucketName: String,
gcpCredentials: GcpCredentials,
messageOnAuthenticationFailure: String,
override val isPush: Boolean,
override val isEnabled: Boolean,
private val sizeThreshold: Long = BLOB_SIZE_THRESHOLD
) : StorageService {

private val storageOptions by lazy { storageOptions(projectId, gcpCredentials, isPush) }
private val storageOptions by lazy {
storageOptions(projectId, gcpCredentials, messageOnAuthenticationFailure, isPush)
}

override fun load(cacheKey: String): InputStream? {
if (!isEnabled) {
Expand Down Expand Up @@ -153,9 +156,14 @@ internal class GcpStorageService(
private fun storageOptions(
projectId: String,
gcpCredentials: GcpCredentials,
messageOnAuthenticationFailure: String,
isPushSupported: Boolean
): StorageOptions? {
val credentials = credentials(gcpCredentials, isPushSupported) ?: return null
val credentials = credentials(
gcpCredentials,
messageOnAuthenticationFailure,
isPushSupported
) ?: return null
val retrySettings = RetrySettings.newBuilder()
retrySettings.maxAttempts = 3
return StorageOptions.newBuilder().setCredentials(credentials)
Expand All @@ -165,8 +173,66 @@ internal class GcpStorageService(
.build()
}

/**
* Attempts to use reflection to clear the cached credentials inside the Google authentication library.
*/
private fun clearCachedDefaultCredentials() {
try {
val field = GoogleCredentials::class.java.getDeclaredField("defaultCredentialsProvider")
field.isAccessible = true
val defaultCredentialsProvider = field.get(null)
val cachedCredentials = field.type.getDeclaredField("cachedCredentials")
cachedCredentials.isAccessible = true
cachedCredentials.set(defaultCredentialsProvider, null)
} catch (exception: Exception) {
// unable to clear the credentials, oh well.
}
}

private fun defaultApplicationGcpCredentials(
scopes: List<String>,
messageOnAuthenticationFailure: String,
forceClearCache: Boolean
): GoogleCredentials {
if (forceClearCache) clearCachedDefaultCredentials()
val credentials = GoogleCredentials.getApplicationDefault().createScoped(scopes)

try {
// If the credentials have expired,
// reauth is required by the user to be able to generate or refresh access token;
// Refreshing the access token here helps us to provide a useful error message to the user
// in case the credentials have expired
credentials.refreshIfExpired()
} catch (e: Exception) {
if (forceClearCache) {
throw Exception(messageOnAuthenticationFailure)
} else {
return defaultApplicationGcpCredentials(
scopes,
messageOnAuthenticationFailure,
forceClearCache = true
)
}
}
val tokenService = TokenInfoService.tokenService()
val tokenInfoResponse = tokenService.tokenInfo(credentials.accessToken.tokenValue).execute()
if (!tokenInfoResponse.isSuccessful) {
if (forceClearCache) {
throw Exception(messageOnAuthenticationFailure)
} else {
return defaultApplicationGcpCredentials(
scopes,
messageOnAuthenticationFailure,
forceClearCache = true
)
}
}
return credentials
}

private fun credentials(
gcpCredentials: GcpCredentials,
messageOnAuthenticationFailure: String,
isPushSupported: Boolean
): GoogleCredentials? {
val scopes = mutableListOf(
Expand All @@ -177,26 +243,7 @@ internal class GcpStorageService(
}
return when (gcpCredentials) {
is ApplicationDefaultGcpCredentials -> {
val credentials = GoogleCredentials.getApplicationDefault().createScoped(scopes)
try {
// If the credentials have expired,
// reauth is required by the user to be able to generate or refresh access token;
// Refreshing the access token here helps us to provide a useful error message to the user
// in case the credentials have expired
credentials.refreshIfExpired()
} catch (e: Exception) {
throw Exception("""
"Your GCP Credentials have expired.
Please regenerate credentials and try again.
""".trimIndent()
)
}
val tokenService = TokenInfoService.tokenService()
val tokenInfoResponse = tokenService.tokenInfo(credentials.accessToken.tokenValue).execute()
if(!tokenInfoResponse.isSuccessful) {
throw GradleException(tokenInfoResponse.errorBody().toString())
}
credentials
defaultApplicationGcpCredentials(scopes, messageOnAuthenticationFailure, forceClearCache = false)
}
is ExportedKeyGcpCredentials -> {
val contents = gcpCredentials.credentials.invoke()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class GcpStorageServiceTest {
projectId = PROJECT_ID,
bucketName = BUCKET_NAME,
gcpCredentials = ExportedKeyGcpCredentials(File(serviceAccountPath!!)),
messageOnAuthenticationFailure = "Please re-authenticate",
isPush = true,
isEnabled = true,
sizeThreshold = 0L
Expand All @@ -54,6 +55,7 @@ class GcpStorageServiceTest {
projectId = PROJECT_ID,
bucketName = BUCKET_NAME,
gcpCredentials = ExportedKeyGcpCredentials(File(serviceAccountPath!!)),
messageOnAuthenticationFailure = "Please re-authenticate",
isPush = true,
isEnabled = true,
sizeThreshold = 0L
Expand All @@ -77,6 +79,7 @@ class GcpStorageServiceTest {
projectId = PROJECT_ID,
bucketName = BUCKET_NAME,
gcpCredentials = ExportedKeyGcpCredentials(File(serviceAccountPath!!)),
messageOnAuthenticationFailure = "Please re-authenticate",
isPush = false,
isEnabled = true,
sizeThreshold = 0L
Expand All @@ -96,6 +99,7 @@ class GcpStorageServiceTest {
projectId = PROJECT_ID,
bucketName = BUCKET_NAME,
gcpCredentials = ExportedKeyGcpCredentials(File(serviceAccountPath!!)),
messageOnAuthenticationFailure = "Please re-authenticate",
isPush = true,
isEnabled = true,
sizeThreshold = 0L
Expand All @@ -104,6 +108,7 @@ class GcpStorageServiceTest {
projectId = PROJECT_ID,
bucketName = BUCKET_NAME,
gcpCredentials = ExportedKeyGcpCredentials(File(serviceAccountPath)),
messageOnAuthenticationFailure = "Please re-authenticate",
isPush = false,
isEnabled = true,
sizeThreshold = 0L
Expand All @@ -129,6 +134,7 @@ class GcpStorageServiceTest {
projectId = PROJECT_ID,
bucketName = BUCKET_NAME,
gcpCredentials = ExportedKeyGcpCredentials(File(serviceAccountPath!!)),
messageOnAuthenticationFailure = "Please re-authenticate",
isPush = true,
isEnabled = false,
sizeThreshold = 0L
Expand Down

0 comments on commit 9ac2daf

Please sign in to comment.