Skip to content
Open
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
166 changes: 140 additions & 26 deletions .github/workflows/release_update.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
name: Release update

on:
push:
branches:
- "**"
paths:
- "fastlane/**"
- ".github/workflows/release_update.yml"
workflow_dispatch:
inputs:
subdomain:
description: "Enter the institute's subdomain or all to release update(all -> deploy update for all institute)"
required: true
default: "all"
mode:
description: "Release mode: 'all' releases updates for all institutes. 'batch' releases updates for the provided comma-separated list."
default: "batch"
type: choice
options:
- "all"
- "batch"
subdomains:
description: "Batch mode only: comma-separated subdomains (example: inst1,inst2,inst3). Can contain a single subdomain."
required: false
default: ""

release_option:
description: "Choose the type of release. 'Completed' for a full release or 'Draft' for a pre-release version."
Expand All @@ -18,38 +31,25 @@ on:

jobs:
release_update:
if: ${{ github.event_name == 'workflow_dispatch' }}
runs-on: ubuntu-latest
env:
GITHUB_ACCESS_KEY: ${{ secrets.GH_ACCESS_KEY }}
GITHUB_USERNAME: ${{ secrets.GH_USERNAME }}
API_ACCESS_KEY: ${{ secrets.API_ACCESS_KEY }}

steps:
- name: Validate branch based on subdomain
- name: Validate inputs
run: |
echo "Current branch: $GITHUB_REF"
SUBDOMAIN="${{ github.event.inputs.subdomain }}"
BRANCH_NAME="${GITHUB_REF#refs/heads/}"
# Define the special subdomains
SPECIAL_SUBDOMAINS="brilliantpalalms brilliantpalaelearn race"
if echo "$SPECIAL_SUBDOMAINS" | grep -w "$SUBDOMAIN" > /dev/null; then
if [ "$BRANCH_NAME" != "$SUBDOMAIN" ]; then
RED='\033[0;31m'
NC='\033[0m' # No Color
echo -e "${RED}ERROR: For subdomain '$SUBDOMAIN', workflow must run on branch '$SUBDOMAIN'. Current branch is '$BRANCH_NAME'.${NC}"
echo -e "${RED}This is because the '$SUBDOMAIN' institute has a customized UI maintained in a separate branch named '$SUBDOMAIN'.${NC}"
echo -e "${RED}Please merge the latest changes from 'master' into the '$SUBDOMAIN' branch and run this release update from that branch.${NC}"
MODE="${{ github.event.inputs.mode }}"
SUBDOMAINS="${{ github.event.inputs.subdomains }}"
if [ "$MODE" = "batch" ]; then
if [ -z "$(echo "$SUBDOMAINS" | tr -d '[:space:]')" ]; then
echo "ERROR: mode=batch requires non-empty 'subdomains' input (comma-separated)."
exit 1
fi
else
echo "Subdomain '$SUBDOMAIN' can run on any branch. Current branch is '$BRANCH_NAME'."
fi

- name: Validate subdomain
if: ${{ github.event.inputs.subdomain }} != 'all'
run: |
curl -f https://${{ github.event.inputs.subdomain }}.testpress.in/api/v2.5/admin/android/app-config/ -H "API-access-key: $API_ACCESS_KEY"

- name: Setup JDK 17
uses: actions/setup-java@v3
with:
Expand Down Expand Up @@ -93,17 +93,131 @@ jobs:
wget https://media.testpress.in/static/android/zoom_sdk.zip
unzip -o ./zoom_sdk.zip

- name: Build and deploy app
- name: Build and deploy app (all)
if: ${{ github.event.inputs.mode == 'all' }}
run: |
export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8
bundle exec fastlane release_update_to subdomain:${{ github.event.inputs.subdomain }} release_option:${{ github.event.inputs.release_option }}
bundle exec fastlane release_update_to subdomain:all release_option:${{ github.event.inputs.release_option }}

- name: Build and deploy app (batch)
if: ${{ github.event.inputs.mode == 'batch' }}
run: |
export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8
bundle exec fastlane release_update_batch subdomains:"${{ github.event.inputs.subdomains }}" release_option:${{ github.event.inputs.release_option }}

- name: Store app artifacts
if: ${{ always() && github.event.inputs.mode == 'all' }}
uses: actions/upload-artifact@v4
with:
name: app
path: |
app/build/outputs/apk/release/app-release.apk
app/build/outputs/bundle/release/app-release.aab
app/build/outputs/apk/debug/app-debug.apk

- name: Store batch app artifacts
if: ${{ always() && github.event.inputs.mode == 'batch' }}
uses: actions/upload-artifact@v4
with:
name: batch-app-artifacts
path: |
batch_artifacts

- name: Store batch summary
if: ${{ always() && github.event.inputs.mode == 'batch' }}
uses: actions/upload-artifact@v4
with:
name: batch-summary
path: |
release_update_batch_summary.json

push_batch_build:
if: ${{ github.event_name == 'push' }}
runs-on: ubuntu-latest
env:
GITHUB_ACCESS_KEY: ${{ secrets.GH_ACCESS_KEY }}
GITHUB_USERNAME: ${{ secrets.GH_USERNAME }}
API_ACCESS_KEY: ${{ secrets.API_ACCESS_KEY }}

steps:
- name: Checkout the repository
uses: actions/checkout@v4

- name: Validate secrets
run: |
if [ -z "$(echo "$API_ACCESS_KEY" | tr -d '[:space:]')" ]; then
echo "ERROR: API_ACCESS_KEY is not available."
exit 1
fi
if [ -z "$(echo "$GITHUB_USERNAME" | tr -d '[:space:]')" ]; then
echo "ERROR: GITHUB_USERNAME is not available (required for GitHub Packages Maven auth)."
exit 1
fi
if [ -z "$(echo "$GITHUB_ACCESS_KEY" | tr -d '[:space:]')" ]; then
echo "ERROR: GITHUB_ACCESS_KEY is not available (required for GitHub Packages Maven auth)."
exit 1
fi

- name: Setup JDK 17
uses: actions/setup-java@v3
with:
distribution: "temurin"
java-version: 17

- name: Setup Android SDK
uses: android-actions/setup-android@v2

- name: Caching Gradle packages
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-

- name: Setup ruby and fastlane
uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7
bundler-cache: true

- name: Cache Zoom SDK files
id: zoom-cache
uses: actions/cache@v3
with:
path: |
./mobilertc
./commonlib
key: zoom_sdk_files

- name: Setup Zoom SDK
if: steps.zoom-cache.outputs.cache-hit != 'true'
run: |
wget https://media.testpress.in/static/android/zoom_sdk.zip
unzip -o ./zoom_sdk.zip

- name: Batch release (draft)
run: |
export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8
bundle exec fastlane release_update_batch subdomains:"lms,lmsdemo" release_option:draft

- name: Store push batch app artifacts
if: ${{ always() }}
uses: actions/upload-artifact@v4
with:
name: push-batch-app-artifacts
path: |
batch_artifacts

- name: Store push batch release summary
if: ${{ always() }}
uses: actions/upload-artifact@v4
with:
name: push-batch-release-summary
path: |
release_update_batch_summary.json
111 changes: 111 additions & 0 deletions fastlane/Fastfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,120 @@
require 'json'
require 'fileutils'
require 'time'

desc "Build customized app for an institute"
lane :build_app_files do |params|
app_config = get_app_config(subdomain: params[:subdomain])
generate_customized_app_files(app_config: app_config, split_apk: params[:split_apk])
end

desc "Release updates for a comma-separated batch of subdomains (always continues on error)"
lane :release_update_batch do |params|
raw_subdomains = params[:subdomains].to_s
release_option = params[:release_option]

subdomains = raw_subdomains
.split(/[,\s]+/)
.map { |s| s.strip.downcase }
.reject(&:empty?)
.uniq

if subdomains.empty?
UI.user_error!("No subdomains provided. Pass subdomains:\"inst1,inst2,inst3\"")
end

current_branch = sh("git rev-parse --abbrev-ref HEAD", log: false).strip
special_branch_subdomains = %w[brilliantpalalms brilliantpalaelearn race]

started_at = Time.now
succeeded = []
failed = []
skipped = []

expected_artifacts = [
{ "src" => File.join("app", "build", "outputs", "bundle", "release", "app-release.aab"), "dst" => "app-release.aab" },
{ "src" => File.join("app", "build", "outputs", "apk", "release", "app-release.apk"), "dst" => "app-release.apk" },
{ "src" => File.join("app", "build", "outputs", "apk", "debug", "app-debug.apk"), "dst" => "app-debug.apk" }
].freeze

subdomains.each do |subdomain|
if special_branch_subdomains.include?(subdomain) && current_branch != subdomain
skipped << {
"subdomain" => subdomain,
"reason" => "Requires running on branch '#{subdomain}' (current: '#{current_branch}')"
}
next
end

current_step = "validate_subdomain"
begin
config = get_app_config(subdomain: subdomain)
unless config.is_a?(Hash) && config["package_name"].to_s.strip != ""
raise "Invalid app-config response (missing package_name)"
end

current_step = "update_app_version"
app_config = update_app_version(subdomain: subdomain)

current_step = "generate_customized_app_files"
generate_customized_app_files(app_config: app_config)

current_step = "collect_artifacts"
artifact_root = File.join("batch_artifacts", subdomain)
FileUtils.mkdir_p(artifact_root)

missing = []
expected_artifacts.each do |artifact|
src = artifact["src"]
if File.exist?(src)
FileUtils.cp(src, File.join(artifact_root, artifact["dst"]))
else
missing << src
end
end
raise "Missing build artifacts: #{missing.join(', ')}" if missing.any?

current_step = "deploy_app"
deploy_app(
play_console_key_file: app_config["play_console_key_file"],
package_name: app_config["package_name"],
release_option: release_option
)

succeeded << subdomain
rescue => e
failed << { "subdomain" => subdomain, "step" => current_step, "error" => e.message }
end
end

ended_at = Time.now
summary = {
"mode" => "batch",
"release_option" => release_option,
"branch" => current_branch,
"started_at" => started_at.iso8601,
"ended_at" => ended_at.iso8601,
"duration_seconds" => (ended_at - started_at).to_i,
"requested_subdomains" => subdomains,
"succeeded" => succeeded,
"failed" => failed,
"skipped" => skipped,
"counts" => {
"requested" => subdomains.length,
"succeeded" => succeeded.length,
"failed" => failed.length,
"skipped" => skipped.length
}
}

File.write("release_update_batch_summary.json", JSON.pretty_generate(summary))

UI.message("Batch release summary: requested=#{subdomains.length}, succeeded=#{succeeded.length}, failed=#{failed.length}, skipped=#{skipped.length}")
if failed.any?
UI.user_error!("Batch release completed with failures. See release_update_batch_summary.json")
end
end

lane :release_update_to do |params|
release_option = params[:release_option]
if params[:subdomain] == "all"
Expand Down
3 changes: 3 additions & 0 deletions fastlane/actions/fetch_subdomains.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ def self.run(params)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
response = http.request(request)
unless response.is_a?(Net::HTTPSuccess)
UI.user_error!("Fetch subdomains failed: HTTP #{response.code} - #{response.body}")
end
return JSON.parse(response.body)
end
end
Expand Down
3 changes: 3 additions & 0 deletions fastlane/actions/get_app_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ def self.run(params)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
response = http.request(request)
unless response.is_a?(Net::HTTPSuccess)
UI.user_error!("App config validation failed for '#{params[:subdomain]}': HTTP #{response.code} - #{response.body}")
end
return JSON.parse(response.body)
end

Expand Down
3 changes: 3 additions & 0 deletions fastlane/actions/update_app_version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ def self.run(params)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
response = http.request(request)
unless response.is_a?(Net::HTTPSuccess)
UI.user_error!("Android update failed for '#{params[:subdomain]}': HTTP #{response.code} - #{response.body}")
end
return JSON.parse(response.body)
end

Expand Down
Loading