Computer >> 컴퓨터 >  >> 체계 >> Android

Android App Bundle을 Play 스토어에 자동으로 업로드하는 방법

이 글에서는 안드로이드 앱 번들(.aab 파일)을 플레이스토어 베타 트랙에 자동으로 업로드하는 방법을 설명하겠다. Android Studio와 AWS를 클라우드 인프라 제공업체로 사용할 것입니다.

App Bundle을 업로드하면 Slack 알림이 실행됩니다.

이는 관찰 가능성 생성 및 프로세스 우선 순위 지정과 같은 여러 가지 이유로 귀중한 시간 사용입니다.

사용할 기술

이 튜토리얼에서 사용할 리소스는 다음과 같습니다.

  1. Android 스튜디오
  2. AWS 코드빌드
  3. AWS 람다
  4. S3
  5. 슬랙

프로젝트 개요

Android App Bundle을 Play 스토어에 자동으로 업로드하는 방법

위 이미지는 전체 구성 방법에 대한 일반적인 개요를 보여줍니다.

기본적으로 Android 리포지토리에 대해 AWS에 코드 파이프라인을 설정해야 합니다. 이 코드 파이프라인에는 코드 빌드가 단계 중 하나로 포함됩니다.

Android 앱 리포지토리의 마스터 분기로 푸시하면 코드 빌드가 트리거됩니다. Code Build 프로젝트는 명령줄에서 Android 앱에 서명하고 아티팩트를 S3 버킷에 업로드합니다.

번들을 S3에 업로드하면 Lambda가 트리거되고, 이 번들을 다운로드하고 Google Publishing API를 사용하여 Play 스토어에 업로드합니다. 200 응답을 받으면 Lambda가 Slack 알림을 트리거합니다.

Google Play 서비스 계정 키를 얻는 방법

Google Play 게시자 API를 사용하려면 Google Play 서비스 계정 키가 필요합니다.

서비스 계정은 서버가 서로 통신할 때 사용자를 대신할 수 있는 계정입니다. 여기에서 Google이 서버 간 통신에 OAuth2.0을 사용하는 방법에 대해 자세히 알아볼 수 있습니다.

서비스 계정을 만들고 Google Play Publisher API에 대한 액세스 권한을 부여하는 방법을 보려면 여기를 참조하세요.

서비스 계정을 만들고 적절한 권한을 부여했으면 서비스 계정 키를 다운로드하여 안전하게 보관하십시오. 곧 S3 버킷에 업로드할 예정입니다.

Android 번들에 서명하는 방법

가장 중요한 것은 Android App Bundle에 서명하는 방법입니다. Google에는 여기에서 찾을 수 있는 상당히 괜찮은 문서가 있습니다.

아래에 링크를 요약하겠습니다.

keytool을 사용하여 개인 키 생성 이렇게:

keytool -genkey -v -keystore my-release-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias my-alias

원하는 대로 키를 부를 수 있습니다. 여기서는 my-release-key.jks라고 불렀습니다. . 원하는 별칭을 선택할 수도 있습니다. 이 튜토리얼 전체에서 키에 대해 올바른 이름과 별칭을 사용해야 합니다.

build.gradle 열기 app 내 Android Studio의 디렉토리에 다음 코드 블록을 추가합니다.

android {
    ...
    defaultConfig { ... }
    signingConfigs {
        release {
            // You need to specify either an absolute path or include the
            // keystore file in the same directory as the build.gradle file.
            storeFile file("my-release-key.jks")
            storePassword "password"
            keyAlias "my-alias"
            keyPassword "password"
        }
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release
            ...
        }
    }
}

릴리스 키의 이름을 기본값이 아닌 다른 이름으로 변경한 경우 새 이름을 지정해야 합니다. 별칭도 마찬가지입니다.

스토어 비밀번호는 앱을 Play 스토어에 처음 업로드할 때 생성한 비밀번호입니다.

이제 ./gradlew :app:bundleRelease 명령을 실행하면 Android Studio의 명령줄에서 서명된 App Bundle을 생성하는 것을 알 수 있습니다.

서명 정보 삭제 방법

build.gradle에서 일반 텍스트로 사용할 수 있는 서명 정보로 코드 커밋 파일은 보안 위험 요소이며 공격 벡터가 될 수 있습니다.

Google에는 여기에서 찾을 수 있는 관련 문서가 있습니다.

먼저 keystore.properties를 만듭니다. 프로젝트 디렉토리의 루트에 있는 파일입니다.

파일 내용은 다음과 같아야 합니다.

storePassword=myStorePassword
keyPassword=myKeyPassword
keyAlias=myKeyAlias
storeFile=myStoreFileLocation

스토어 비밀번호와 키 비밀번호는 App Bundle을 App Store에 처음 업로드할 때 사용한 비밀번호입니다.

귀하의 keyAliasstoreFile 개인 키를 생성할 때 할당한 별칭과 생성한 개인 키의 위치가 각각 됩니다.

이제 이 파일을 build.gradle에 로드해야 합니다. . 이것은 처음에는 놀라웠지만 Gradle은 실제로 DSL로 작동합니다. 따라서 Gradle을 사용하여 구성을 더 쉽게 작성할 수 있습니다.

//  Load properties from keystore.properties
def keystorePropertiesFile = rootProject.file("keystore.properties")

//  Creating a new Properties() object
def keystoreProperties = new Properties()

//  If keystorePropertiesFile exists, read from that, else set from build environment
if (keystorePropertiesFile.exists()) {
    //  Loading the keystoreProperties file
    keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
} else {
    //  Read all environment variables from the build environment
    keystoreProperties.setProperty("storeFile", "${System.getenv('STORE_FILE')}")
    keystoreProperties.setProperty("keyAlias", "${System.getenv('KEY_ALIAS')}")
    keystoreProperties.setProperty("keyPassword", "${System.getenv('KEY_PASSWORD')}")
    keystoreProperties.setProperty("storePassword", "${System.getenv('STORE_PASSWORD')}")
}

거기에 if 조건이 있음을 알 수 있습니다. 지금은 그것에 대해 걱정하지 마십시오. 나중에 코드 빌드를 설명하기 위해 특별히 존재합니다.

이렇게 하면 signingConfigs를 변경합니다. build.gradle 섹션 다음과 같이 표시됩니다.

signingConfigs {
        release {
            storeFile file(keystoreProperties['storeFile'])
            keyAlias keystoreProperties['keyAlias']
            keyPassword keystoreProperties['keyPassword']
            storePassword keystoreProperties['storePassword']
        }
    }

AWS 코드 파이프라인을 설정하는 방법

비교적 간단하기 때문에 이에 대해 너무 자세히 설명하지는 않겠습니다.

다음 세 단계로 AWS Code Pipeline을 설정하십시오.

  1. GitHub 저장소의 master에 연결된 소스 단계 지점
  2. AWS Code Build에 연결된 빌드 단계
  3. S3 버킷에 배포할 배포 단계입니다.

여기에서 코드 파이프라인 설정에 대한 추가 문서를 찾을 수 있습니다.

AWS S3 설정 방법

먼저 코드 빌드를 단계 중 하나로 사용하여 코드 파이프라인을 설정했는지 확인합니다. 다음으로 두 개의 S3 버킷을 설정합니다.

  1. 릴리스 키를 저장할 버킷입니다. 저는 이 버킷을 release-key.jks이라고 부릅니다.
  2. Google Play 서비스 계정 비공개 키를 저장할 버킷입니다. (서비스 계정을 만드는 동안 이 키를 다운로드했어야 합니다.)

Code Build 서비스 역할에서 이러한 버킷에 대한 액세스를 허용해야 합니다. 코드 파이프라인을 설정할 때 코드 빌드 서비스 역할이 생성되어야 합니다.

IAM 콘솔로 이동하여 Code Build 서비스 역할을 찾고 ARN을 가져옵니다.

그런 다음 콘솔을 사용하여 버킷 release-key.jks에 대한 권한 탭으로 이동합니다. 거기에 다음 정책을 추가하십시오.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": [
                    "arn:aws:iam::123456789:role/service-role/codebuild-service-role-dummy",
                ]
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::release-key-bucket/*"
        }
    ]
}

이 정책은 CodeBuild 프로젝트가 실행될 머신에서 S3 버킷에 대한 액세스를 허용합니다.

위에서 언급한 ARN을 계정의 ARN으로 교체해야 합니다. 정책을 업데이트할 때 Code Build 서비스 역할에 대해 올바른 ARN을 지정해야 합니다.

두 번째 버킷에 대한 권한 정책을 변경할 필요가 없습니다. 버킷에 액세스할 수 있도록 AWS Lambda 역할에 관련 권한을 추가하겠습니다.

AWS CodeBuild를 설정하는 방법

다음으로 buildspec.yml을 만듭니다. 프로젝트 루트 폴더에 있는 파일입니다.

version: 0.2

phases:
  build:
    commands:
      - aws s3api get-object --bucket release-key.jks --key release-key.jks ./releaseKey.jks
      - cp ./releaseKey.jks ${CODEBUILD_SRC_DIR}/app/releaseKey.jks
      - export STORE_FILE=releaseKey.jks
      - export KEY_ALIAS=$keyAlias
      - export KEY_PASSWORD=$keyPassword
      - export STORE_PASSWORD=$storePassword
      - ./gradlew :app:bundleRelease

artifacts:
  files:
    - app/build/outputs/bundle/release/app-release.aab

이 파일은 매우 간단합니다. 지정된 버킷에서 릴리스 키를 가져와서 지정된 위치에 있는 Code Build 서버의 로컬 파일에 저장합니다.

다음으로 build.gradle에 필요한 모든 변수를 내보냅니다. 올바르게 작동하도록 구성하십시오. 마지막으로 명령줄에서 Gradle의 릴리스 명령을 실행합니다.

코드 빌드에서 이 스크립트를 실행하려면 먼저 코드 빌드 환경에 변수를 추가해야 합니다. 이렇게 하려면 먼저 AWS Code Build 콘솔로 이동하여 Android 앱용 빌드 프로젝트를 선택하십시오.

그런 다음 아래 스크린샷과 같이 편집> 환경을 선택합니다.

Android App Bundle을 Play 스토어에 자동으로 업로드하는 방법

이 작업을 수행하면 나타나는 화면에서 추가 구성 드롭다운을 선택합니다. 키 값 쌍을 통해 환경 변수를 추가하는 옵션이 표시됩니다.

이제 Code Build가 buildspec.yml를 실행할 때 파일에서 지정한 변수를 내보낼 수 있습니다.

현재 상황에서 파이프라인이 실행되면 Code Build가 개인 키를 다운로드하여 Android 앱에 서명 및 빌드하고 서명된 번들을 S3 버킷에 업로드할 수 있습니다.

Slack 앱 설정 방법

관찰 가능성은 자동화의 특징입니다. 자동화가 실행되는 시기, 성공 여부, 실패할 경우 실패 이유를 알고 싶습니다.

AWS가 일반적으로 관측 가능성을 처리하는 방식은 CloudWatch를 통하는 것입니다. 하지만 Slack 통합도 그 목적에 부합한다고 생각합니다.

Slack을 자동화 워크플로에 통합하는 가장 쉬운 방법은 Slack 앱을 설정하고 자동화 워크플로에서 해당 앱으로 알림을 보내는 것입니다.

Slack 앱을 설정하는 방법을 알아보려면 여기에서 설명서를 참조하세요. 프로세스는 매우 간단하며 몇 분 안에 앱을 실행하고 실행할 수 있습니다.

앱을 생성하면 관련 채널에 게시하기 위해 앱을 호출하는 데 사용할 수 있는 WebHook URL이 제공됩니다. AWS Lambda 함수와 함께 사용할 것이기 때문에 이 WebHook URL을 추적하십시오.

AWS Lambda를 설정하는 방법

지금까지 S3 버킷에 서명, 빌드 및 업로드되는 Android App Bundle이 있습니다. 다음으로 Play 스토어의 베타 트랙에 번들을 업로드하는 방법을 알아내야 합니다.

이를 수행하는 방법은 번들이 S3 버킷에 업로드될 때 트리거될 AWS Lambda를 설정하는 것입니다. 이 트리거가 발생하면 Lambda가 실행되고 번들을 다운로드하고 서비스 계정 키를 가져와 Play 스토어 베타 트랙에 번들을 업로드합니다.

Lambda를 생성하고 파일이 버킷에 업로드될 때 실행하도록 트리거를 추가했으면 아래 코드를 살펴보십시오.

"""This Python3 script is used to upload a new .aab bundle to the play store. The execution of this Python script
    occurs through an AWS Lambda which is invoked when a new file is uploaded to the relevant S3 buckets"""

import json
import boto3
import os
from urllib import request, parse
from google.oauth2 import service_account
import googleapiclient.discovery

#   Defining the scope of the authorization request
SCOPES = ['https://www.googleapis.com/auth/androidpublisher']

#   Package name for app
package_name = 'com.app.name'

#   Define the slack webhook url
slack_webhook_url = os.environ['SLACK_WEBHOOK_URL']

def send_slack_message(message):
    data = json.dumps({ 'text': message })
    post_data = data.encode('utf-8')
    req = request.Request(slack_webhook_url, data=post_data, headers={ 'Content-Type': 'application/json' })
    request.urlopen(req)

#   This is the main handler function
def lambda_handler(event, context):
    #   Create a new client S3 client and download the correct file from the bucket
    s3 = boto3.client('s3')
    s3.download_file('service-account-bucket-key', 'service-account-bucket-key.json', '/tmp/service-account-key.json')
    SERVICE_ACCOUNT_FILE = '/tmp/service-account-key.json'

    #   Download the app-release.aab file that triggered the Lambda
    bucket_name = event['Records'][0]['s3']['bucket']['name']
    file_key = event['Records'][0]['s3']['object']['key']
    s3.download_file(bucket_name, file_key, '/tmp/app-release.aab')
    APP_BUNDLE = '/tmp/app-release.aab'

    print(f"A bundle uploaded to {bucket_name} has triggered the Lambda")

    #   Create a credentials object and create a service object using the credentials object
    credentials = service_account.Credentials.from_service_account_file(
        SERVICE_ACCOUNT_FILE, scopes=SCOPES
    )
    service = googleapiclient.discovery.build('androidpublisher', 'v3', credentials=credentials, cache_discovery=False)
    
    #   Create an edit request using the service object and get the editId
    edit_request = service.edits().insert(body={}, packageName=package_name)
    result = edit_request.execute()
    edit_id = result['id']

    #   Create a request to upload the app bundle
    try:
        bundle_response = service.edits().bundles().upload(
            editId=edit_id,
            packageName=package_name,
            media_body=APP_BUNDLE,
            media_mime_type="application/octet-stream"
        ).execute()
    except Exception as err:
        message = f"There was an error while uploading a new version of {package_name}"
        send_slack_message(message)
        raise err

    print(f"Version code {bundle_response['versionCode']} has been uploaded")

    #   Create a track request to upload the bundle to the beta track
    track_response = service.edits().tracks().update(
        editId=edit_id,
        track='beta',
        packageName=package_name,
        body={u'releases': [{
            u'versionCodes': [str(bundle_response['versionCode'])],
            u'status': u'completed',
        }]}
    ).execute()

    print("The bundle has been committed to the beta track")

    #   Create a commit request to commit the edit to BETA track
    commit_request = service.edits().commit(
        editId=edit_id,
        packageName=package_name
    ).execute()

    print(f"Edit {commit_request['id']} has been committed")

    message = f"Version code {bundle_response['versionCode']} has been uploaded from the bucket {bucket_name}.\nEdit {commit_request['id']} has been committed"
    send_slack_message(message)
    
    return {
        'statusCode': 200,
        'body': json.dumps('Successfully executed the app bundle release to beta')
    }

위의 Lambda는 googleapiclient를 사용합니다. 라이브러리 및 해당 검색 모듈을 사용하여 Google Play의 Publishing API용 URL을 빌드합니다.

그런 다음 Lambda는 이전에 설정한 버킷에서 서비스 계정 키를 다운로드합니다. 올바른 버킷 이름을 지정했는지 확인해야 합니다.

업로드의 성공 여부에 따라 Slack 메시지가 출력되기를 원합니다. 이전 섹션의 Slack WebHook URL을 Lambda의 환경 변수에 추가합니다. 위의 함수는 Python의 os를 사용합니다. 모듈을 사용하여 환경 변수에 액세스하고 메시지를 Slack에 게시합니다.

Lambda가 실패하면 Lambda에 Google Play 서비스 계정의 키가 저장된 S3 버킷에 액세스할 수 있는 권한이 없기 때문일 수 있습니다. 이 경우 이를 나타내는 오류 메시지가 표시됩니다.

이 문제를 해결하려면 관련 권한을 Lambda 역할에 추가하기만 하면 됩니다.

추가해야 하는 정책은 다음과 같습니다.

{
    "Version": "2012-10-07",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObjectVersion",
                "s3:GetBucketVersioning",
                "s3:GetBucketAcl",
                "s3:GetObject",
                "s3:GetBucketTagging",
                "s3:GetBucketLocation",
                "s3:GetObjectVersionAcl"
            ],
            "Resource": [
                "arn:aws:s3:::arn:aws:s3:::your-bucket-name-with-service-account-key"
            ]
        }
    ]
}

버킷의 ARN을 계정의 관련 ARN으로 바꾸면 바로 사용할 수 있습니다.

결론

그래서, 당신은 그것을 가지고 있습니다. 확실히 쉽지는 않았고 움직이는 부분도 많았지만 자동화로 시간과 노력을 많이 절약할 수 있었습니다.

새 앱 업데이트를 자주 릴리스하는 팀의 일원이라면 업데이트를 릴리스하는 업무를 담당하는 사람이 한 명도 없다는 이유로 방해받고 싶지 않을 것입니다.

이러한 종류의 자동화를 구축하면 CI/CD 워크플로가 훨씬 더 부드럽고 강력해집니다.

이와 같은 블로그에 관심이 있으시면 https://redixhumayun.github.io에서 더 많은 정보를 읽거나 Twitter에서 저를 팔로우할 수 있습니다.