Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package api.bitrise.private

import api.bitrise.private.CookieRetrofit.privateCookieRetrofit
import retrofit2.Call
import retrofit2.Response
import retrofit2.http.*

object RollingBuilds {
Expand All @@ -41,30 +42,46 @@ object RollingBuilds {
privateCookieRetrofit.create(ApiService::class.java)
}

// 404 error - means BITRISE_USER isn't authorized to access that app.
// TODO: move this to an interceptor
private fun <T> Response<T>.checkError(): Response<T> {
val response = this
val request = response.raw().request()

if (response.isSuccessful.not()) {
throw RuntimeException("""
${request.method()} ${request.url()} ${response.code()} ${response.message()}
${response.errorBody()?.string()}
""".trimIndent())
}

return response
}

fun getConfig(appSlug: String): RollingBuildsConfig {
return apiService.getConfig(appSlug).execute().body() ?: throw RuntimeException("getConfig failed")
return apiService.getConfig(appSlug).execute().checkError().body() ?: throw RuntimeException("getConfig failed")
}

fun setConfigPR(appSlug: String, enabled: Boolean): RollingBuildsConfig {
val config = RollingBuildsPatch("pr", enabled)
return apiService.setConfig(appSlug, config).execute().body() ?: throw RuntimeException("setConfigPR failed")
return apiService.setConfig(appSlug, config).execute().checkError().body() ?: throw RuntimeException("setConfigPR failed")
}

fun setConfigPush(appSlug: String, enabled: Boolean): RollingBuildsConfig {
val config = RollingBuildsPatch("push", enabled)
return apiService.setConfig(appSlug, config).execute().body() ?: throw RuntimeException("setConfigPush failed")
return apiService.setConfig(appSlug, config).execute().checkError().body() ?: throw RuntimeException("setConfigPush failed")
}

fun setConfigRunning(appSlug: String, enabled: Boolean): RollingBuildsConfig {
val config = RollingBuildsPatch("running", enabled)
return apiService.setConfig(appSlug, config).execute().body() ?: throw RuntimeException("setConfigRunning failed")
return apiService.setConfig(appSlug, config).execute().checkError().body() ?: throw RuntimeException("setConfigRunning failed")
}

fun disable(appSlug: String): Status {
return apiService.disable(appSlug).execute().body() ?: Status(null, null)
return apiService.disable(appSlug).execute().checkError().body() ?: Status(null, null)
}

fun enable(appSlug: String): Status {
return apiService.enable(appSlug).execute().body() ?: Status(null, null)
return apiService.enable(appSlug).execute().checkError().body() ?: Status(null, null)
}
}
111 changes: 111 additions & 0 deletions automation/cloud_build_metrics/docs/BackupBitriseYamls.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# BackupBitriseYamls

## Objective

All workflows should be backed up to an encrypted git repository.

![](./png/keybase_git_repo.png)

## Solution

The [Keybase client](https://github.com/keybase/client) provides a cloud encrypted git repo.
Using the bitrise API, all YAMLs are fetched and then committed to the keybase git repo.
See [BackupBitriseYamls.kt][1] for the full source. Note that you should create a paper key when using keybase.
Otherwise if the device you installed keybase on is lost, you won't be able to login anymore.

```kotlin
override fun execute() {
val apps = BitriseApps.getAppsForOrg()
val appsMap = appsMap(apps)
val repo = keybaseRepo()
writeYamls(repo, apps)
}
```

[1]: https://github.com/instructure/canvas-android/blob/f455db88520d37be007af2f7b9e36d17e45182f5/automation/cloud_build_metrics/src/main/kotlin/tasks/BackupBitriseYamls.kt

The update happens in a scheduled nightly job on Bitrise using this workflow:

```yaml
prepare_keybase:
envs:
- KEYBASE_PAPERKEY: ...
opts:
is_expand: false
- KEYBASE_USERNAME: ...
opts:
is_expand: false
steps:
- script:
inputs:
- content: |
#!/bin/bash
set -e

if [ ! -d keybase ]; then
echo "Downloading keybase..."
curl --silent --output keybase.deb https://prerelease.keybase.io/keybase_amd64.deb
echo "Installing keybase deb"
mkdir keybase
set +e
dpkg -i keybase.deb
apt-get install -f
dpkg -i keybase.deb
set -e
else
echo "Keybase exists!"
fi
title: Keybase - Download
- script:
inputs:
- content: |
#!/usr/bin/env bash
set -ex

export KEYBASE_ALLOW_ROOT=1

keybase version
keybase oneshot
title: Keybase - Login
- script:
inputs:
- content: |-
#!/usr/bin/env bash
set -ex

git clone keybase://team/team/secrets ../../../secrets
title: Keybase - git clone repo
BackupBitriseYamls:
after_run: []
before_run:
- setup_workflow
- prepare_keybase
steps:
- gradle-runner:
inputs:
- gradle_task: clean fatJar
- gradle_file: "$BITRISE_SOURCE_DIR/automation/cloud_build_metrics/build.gradle.kts"
- gradlew_path: "$BITRISE_SOURCE_DIR/automation/cloud_build_metrics/gradlew"
- cache_level: all
- script:
inputs:
- content: |
#!/bin/bash
set -euxo pipefail
java -version
java -jar "$BITRISE_SOURCE_DIR/automation/cloud_build_metrics/build/libs/cloud_build_metrics-all-1.0-SNAPSHOT.jar" BackupBitriseYamls
title: Backup Bitrise Yamls
- script:
inputs:
- content: |
#!/usr/bin/env bash
set -ex

if [[ -n $(git -C ../../../secrets status -s) ]]; then
DATE=`date '+%Y-%m-%d %H:%M:%S'`
git -C ../../../secrets add .
git -C ../../../secrets commit -m "update bitrise yamls $DATE"
git -C ../../../secrets push
fi
title: Keybase - git commit & push
```
69 changes: 69 additions & 0 deletions automation/cloud_build_metrics/docs/BitriseCacheRefresh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# BitriseCacheRefresh

## Objective

Automatically ensure the Bitrise cache is fresh.

```
App title renamed android-teacher -> Android Teacher. Verify and update.
App title renamed android-parent -> Android Parent. Verify and update.
App title renamed android-student -> Android Student. Verify and update.
App title renamed Android Teacher Espresso -> Android Teacher UI Tests. Verify and update.
App title renamed cloud_build_metrics -> Cloud Build Metrics. Verify and update.

tasks.BitriseCacheRefresh is running...

Android Teacher build cache deleted
1 GB - master
Android Parent build cache deleted
872 MB - master
Android Student build cache deleted
1 GB - master
android-polling build cache deleted
782 MB - master
data seeding api build cache deleted
694 MB - master
Android Teacher UI Tests build cache deleted
1 GB - master
Cloud Build Metrics build cache deleted
709 MB - master
Triggering build for Android Teacher using workflow debug
https://app.bitrise.io/build/48d1dc6727b9ac5b
Triggering build for Android Parent using workflow debug
https://app.bitrise.io/build/d5ae8a3d0c29d745
Triggering build for Android Student using workflow debug
https://app.bitrise.io/build/95b5256a92888729
Triggering build for android-polling using workflow debug
https://app.bitrise.io/build/217aca865c4872ac
Triggering build for data seeding api using workflow primary
https://app.bitrise.io/build/989f8637c8cf06da
Triggering build for Android Teacher UI Tests using workflow primary
https://app.bitrise.io/build/3a4dbc026d5667f1
Triggering build for Cloud Build Metrics using workflow RunUnitTests
https://app.bitrise.io/build/85574c05121ad050

Process finished with exit code 0
```

## Solution

Using the Bitrise private API, we’re able to programmatically delete caches for all jobs that run on pull requests.
To populate the cache, we need to figure out the default workflow for each app. There’s no API support for this,
so the YAML file is downloaded and parsed directly. After determining the workflow, we use Bitrise’s new public API
for scheduling a build. The cache refresh task runs nightly. Each business day, we’re starting with a freshly optimized
cache. This has been a great win for optimizing build times.

```kotlin
override fun execute() {
signIn()
deleteCaches()
populateCaches()
}
```

See [BitriseCacheRefresh.kt][1] for the full source.

[1]: https://github.com/instructure/canvas-android/blob/f455db88520d37be007af2f7b9e36d17e45182f5/automation/cloud_build_metrics/src/main/kotlin/tasks/BitriseCacheRefresh.kt

There's an open feature request to [Improve cache reliability](https://discuss.bitrise.io/t/rethink-the-cache-system-to-be-more-reliable/3290).

60 changes: 60 additions & 0 deletions automation/cloud_build_metrics/docs/BitriseCacheReport.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# BitriseCacheReport

## Objective

Print the cache size for every app. Monitoring cache size helps ensure builds stay fast.

```
tasks.BitriseCacheReport is running...

Android Student UI Tests
1 GB - master
Android Parent UI Tests
945 MB - master
data seeding api
694 MB - master
Canvas iOS Danger
3 GB - master
Cloud Build Metrics
709 MB - master
Android Teacher UI Tests
1 GB - master
android-polling
782 MB - master
Android Parent
872 MB - master
Android Teacher
1 GB - master
Android Student
1 GB - master

Process finished with exit code 0
```

## Solution

The Bitrise private API enables generating an organization wide cache report for each job. This has been helpful in
making sure jobs are cached appropriately and ensuring we’re staying under the 2GB limit.

```kotlin
override fun execute() {
signIn()
val apps = BitriseApps.getOnlyInstructureApps()

for (app in apps) {
val cache = BuildCache.get(app.slug)
if (cache.isNotEmpty()) {
println(app.title)
for (item in cache) {
val size = item.file_size_bytes.humanReadable()
val branch = item.the_cache_item_key
println(" $size - $branch")
}
}
}
}
```

See [BitriseCacheReport.kt][1] for the full source.

[1]: https://github.com/instructure/canvas-android/blob/f455db88520d37be007af2f7b9e36d17e45182f5/automation/cloud_build_metrics/src/main/kotlin/tasks/BitriseCacheReport.kt
55 changes: 55 additions & 0 deletions automation/cloud_build_metrics/docs/BitriseSetRollingBuilds.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# BitriseSetRollingBuilds

## Objective

Automatically set rolling builds on all apps.

```
tasks.BitriseSetRollingBuilds is running...

Updating build config for: earlgrey-2-binary-example
Updating build config for: gwiz-bot
Updated 2 of 50 apps

Process finished with exit code 0
```

## Solution

The Bitrise private API allows us to set rolling builds for every app. This represents a great time savings for our team,
as previously this was done manually.

```kotlin
override fun execute() {
signIn()
val apps = BitriseApps.getOnlyInstructureApps()

var updatedCount = 0
for (app in apps) {
val appSlug = app.slug

val config = try {
RollingBuilds.getConfig(appSlug)
} catch (e: Exception) {
RollingBuilds.enable(appSlug)
RollingBuilds.getConfig(appSlug)
}

if (config.pr && config.push && config.running) continue

println("Updating build config for: ${app.title}")
updatedCount += 1

RollingBuilds.enable(appSlug)
RollingBuilds.setConfigPR(appSlug, true)
RollingBuilds.setConfigPush(appSlug, true)
RollingBuilds.setConfigRunning(appSlug, true)
}

println("Updated $updatedCount of ${apps.size} apps")
}
```

See [BitriseSetRollingBuilds.kt][1] for the full source.

[1]: https://github.com/instructure/canvas-android/blob/f455db88520d37be007af2f7b9e36d17e45182f5/automation/cloud_build_metrics/src/main/kotlin/tasks/BitriseSetRollingBuilds.kt
Loading