-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Upstream processed exec implementation (#20)
* Switch to Path for projectDirOption * Initial port of processed exec * Make issues fully configurable * Standardize envs a bit * Comment a bug * Make JSON more canonical * Add a bugsnag artifact upload failures * Add build scan server to config + test * Fix exit code not falling through * Use duration in message * Spotless
- Loading branch information
Showing
15 changed files
with
958 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
/* | ||
* Copyright (C) 2023 Slack Technologies, LLC | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package slack.cli.exec | ||
|
||
import com.squareup.moshi.Json | ||
import com.squareup.moshi.JsonClass | ||
|
||
/** | ||
* An issue that can be reported to Bugsnag. | ||
* | ||
* @property message the message shown in the bugsnag report message. Should be human-readable. | ||
* @property logMessage the message shown in the CI log when [matchingText] is found. Should be | ||
* human-readable. | ||
* @property matchingText the matching text to look for in the log. | ||
* @property groupingHash grouping hash for reporting to bugsnag. This should usually be unique, but | ||
* can also be reused across issues that are part of the same general issue. | ||
*/ | ||
@JsonClass(generateAdapter = true) | ||
internal data class Issue( | ||
val message: String, | ||
@Json(name = "log_message") val logMessage: String, | ||
@Json(name = "matching_text") val matchingText: String, | ||
@Json(name = "grouping_hash") val groupingHash: String, | ||
@Json(name = "retry_signal") val retrySignal: RetrySignal | ||
) { | ||
|
||
private fun List<String>.checkContains(errorText: String): Boolean { | ||
return any { it.contains(errorText, ignoreCase = true) } | ||
} | ||
|
||
/** Checks the log for this issue and returns a [RetrySignal] if it should be retried. */ | ||
fun check(lines: List<String>, log: (String) -> Unit): RetrySignal { | ||
return if (lines.checkContains(matchingText)) { | ||
log(logMessage) | ||
retrySignal | ||
} else { | ||
RetrySignal.Unknown | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Base class for an issue that can be reported to Bugsnag. This is a [Throwable] for BugSnag | ||
* purposes but doesn't fill in a stacktrace. | ||
*/ | ||
internal class IssueThrowable(issue: Issue) : Throwable(issue.message) { | ||
|
||
override fun fillInStackTrace(): Throwable { | ||
// Do nothing, the stacktrace isn't relevant for these! | ||
return this | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
/* | ||
* Copyright (C) 2023 Slack Technologies, LLC | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package slack.cli.exec | ||
|
||
import kotlin.time.Duration.Companion.minutes | ||
|
||
private const val OOM_GROUPING_HASH = "oom" | ||
|
||
/** A set of known issues. */ | ||
@Suppress("unused") // We look these up reflectively at runtime | ||
internal object KnownIssues { | ||
// A simple fake checker for testing this script | ||
val fakeFailure = | ||
Issue( | ||
message = "Fake failure", | ||
logMessage = "Detected fake failure. Beep boop.", | ||
matchingText = "FAKE FAILURE NOT REAL", | ||
groupingHash = "fake-failure", | ||
retrySignal = RetrySignal.Ack | ||
) | ||
|
||
val ftlRateLimit = | ||
Issue( | ||
message = "FTL rate limit", | ||
matchingText = "429 Too Many Requests", | ||
logMessage = "Detected FTL rate limit. Retrying in 1 minute.", | ||
groupingHash = "ftl-rate-limit", | ||
retrySignal = RetrySignal.RetryDelayed(1.minutes) | ||
) | ||
|
||
val oom = | ||
Issue( | ||
message = "Generic OOM", | ||
matchingText = "Java heap space", | ||
logMessage = "Detected OOM. Retrying immediately.", | ||
groupingHash = OOM_GROUPING_HASH, | ||
retrySignal = RetrySignal.RetryImmediately | ||
) | ||
|
||
val ftlInfrastructureFailure = | ||
Issue( | ||
message = "Inconclusive FTL infrastructure failure", | ||
matchingText = "Infrastructure failure", | ||
logMessage = "Detected inconclusive FTL infrastructure failure. Retrying immediately.", | ||
groupingHash = "ftl-infrastructure-failure", | ||
retrySignal = RetrySignal.RetryImmediately | ||
) | ||
|
||
val flankTimeout = | ||
Issue( | ||
message = "Flank timeout", | ||
groupingHash = "flank-timeout", | ||
matchingText = "Canceling flank due to timeout", | ||
logMessage = "Detected a flank timeout. Retrying immediately.", | ||
retrySignal = RetrySignal.RetryImmediately | ||
) | ||
|
||
val r8Oom = | ||
Issue( | ||
message = "R8 OOM", | ||
matchingText = "Out of space in CodeCache", | ||
logMessage = "Detected a OOM in R8. Retrying immediately.", | ||
groupingHash = OOM_GROUPING_HASH, | ||
retrySignal = RetrySignal.RetryImmediately | ||
) | ||
|
||
val oomKilledByKernel = | ||
Issue( | ||
message = "OOM killed by kernel", | ||
groupingHash = OOM_GROUPING_HASH, | ||
matchingText = "Gradle build daemon disappeared unexpectedly", | ||
logMessage = "Detected a OOM that was killed by the kernel. Retrying immediately.", | ||
retrySignal = RetrySignal.RetryImmediately | ||
) | ||
|
||
val bugsnagUploadFailed = | ||
Issue( | ||
message = "Bugsnag artifact upload failure", | ||
groupingHash = "bugsnag-upload-failure", | ||
matchingText = "Bugsnag request failed to complete", | ||
logMessage = "Detected bugsnag failed to upload. Retrying immediately.", | ||
retrySignal = RetrySignal.RetryImmediately | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
/* | ||
* Copyright (C) 2023 Slack Technologies, LLC | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package slack.cli.exec | ||
|
||
import com.bugsnag.delivery.HttpDelivery | ||
import com.bugsnag.serialization.Serializer | ||
import java.io.IOException | ||
import java.net.Proxy | ||
import java.util.concurrent.TimeUnit | ||
import okhttp3.Authenticator | ||
import okhttp3.Headers.Companion.toHeaders | ||
import okhttp3.MediaType.Companion.toMediaType | ||
import okhttp3.OkHttpClient | ||
import okhttp3.Request | ||
import okhttp3.RequestBody | ||
import okio.BufferedSink | ||
import org.slf4j.LoggerFactory | ||
|
||
/** An [OkHttpClient]-based implementation of Bugsnag's [HttpDelivery]. */ | ||
internal object OkHttpSyncHttpDelivery : HttpDelivery { | ||
private val LOGGER = LoggerFactory.getLogger(OkHttpSyncHttpDelivery::class.java) | ||
private const val DEFAULT_TIMEOUT = 5000L | ||
private const val ENDPOINT = "https://notify.bugsnag.com" | ||
|
||
override fun deliver(serializer: Serializer, payload: Any, headers: Map<String, String>) { | ||
val client = | ||
OkHttpClient.Builder() | ||
.callTimeout(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS) | ||
.proxyAuthenticator(Authenticator.JAVA_NET_AUTHENTICATOR) | ||
.build() | ||
val request = | ||
Request.Builder() | ||
.url(ENDPOINT) | ||
.headers(headers.toHeaders()) | ||
.post( | ||
object : RequestBody() { | ||
override fun contentType() = "application/json".toMediaType() | ||
|
||
override fun writeTo(sink: BufferedSink) { | ||
sink.outputStream().use { serializer.writeToStream(it, payload) } | ||
} | ||
} | ||
) | ||
.build() | ||
|
||
try { | ||
client.newCall(request).execute().use { response -> | ||
if (!response.isSuccessful) { | ||
LOGGER.warn( | ||
"Error not reported to Bugsnag - got non-200 response code: {}", | ||
response.code | ||
) | ||
} | ||
} | ||
} catch (ex: IOException) { | ||
LOGGER.warn("Error not reported to Bugsnag - exception when making request", ex) | ||
} | ||
} | ||
|
||
override fun close() { | ||
// Nothing to do here. | ||
} | ||
|
||
override fun setEndpoint(endpoint: String) { | ||
// Unsupported here | ||
} | ||
|
||
override fun setTimeout(timeout: Int) { | ||
// Unsupported here | ||
} | ||
|
||
override fun setProxy(proxy: Proxy) { | ||
// Unsupported here | ||
} | ||
} |
Oops, something went wrong.