diff --git a/.github/workflows/localization.yml b/.github/workflows/localization.yml index 740d7ef9b..68ce5199b 100644 --- a/.github/workflows/localization.yml +++ b/.github/workflows/localization.yml @@ -1,5 +1,10 @@ on: workflow_dispatch +permissions: + id-token: write + contents: write + pull-requests: write + jobs: Localize: runs-on: ubuntu-22.04 @@ -7,12 +12,40 @@ jobs: steps: - uses: actions/checkout@v3 + + - name: Get access token for TouchDown Build + id: get-token + run: | + # 1. Get the GitHub OIDC federated token + FEDERATED_TOKEN=$(curl -s -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \ + "${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=api://AzureADTokenExchange" | jq -r '.value') + + # 2. Exchange it for a TouchDown Build access token (v2.0 endpoint) + RESPONSE=$(curl -s -X POST \ + "https://login.microsoftonline.com/${{ secrets.AZURE_TENANT_ID }}/oauth2/v2.0/token" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "client_id=${{ secrets.AZURE_CLIENT_ID }}" \ + -d "scope=https://prdtrs01.onmicrosoft.com/touchdownbuildservice_prod/.default" \ + -d "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \ + -d "client_assertion=$FEDERATED_TOKEN" \ + -d "grant_type=client_credentials") + + TOKEN=$(echo "$RESPONSE" | jq -r '.access_token') + + if [ "$TOKEN" = "null" ] || [ -z "$TOKEN" ]; then + echo "ERROR: Failed to get access token" + echo "$RESPONSE" | jq . + exit 1 + fi + + echo "::add-mask::$TOKEN" + echo "TD_ACCESS_TOKEN=$TOKEN" >> "$GITHUB_ENV" + echo "Token acquired successfully (length: ${#TOKEN})" - shell: bash name: Localize env: TDBUILD_TEAM_ID: ${{ secrets.TDBUILD_TEAM_ID }} - TDBUILD_AAD_APPLICATION_CLIENT_ID: ${{ secrets.TDBUILD_AAD_APPLICATION_CLIENT_ID }} - TDBUILD_AAD_APPLICATION_CLIENT_SECRET: ${{ secrets.TDBUILD_AAD_APPLICATION_CLIENT_SECRET }} + TD_ACCESS_TOKEN: ${{ env.TD_ACCESS_TOKEN }} run: ./localize.sh - name: Create Pull Request env: diff --git a/GetLocalizedFiles.sh b/GetLocalizedFiles.sh index c4235c0a3..02cf7705f 100755 --- a/GetLocalizedFiles.sh +++ b/GetLocalizedFiles.sh @@ -1,5 +1,15 @@ #!/bin/bash +# ========================= +# Logging / tracing +# ========================= +set -x +export PS4='+ $(date -u +"%Y-%m-%dT%H:%M:%SZ") GetLocalizedFiles.sh:${LINENO} -> ' + +echo "========== GetLocalizedFiles.sh START ==========" +echo "PWD: $(pwd)" +echo "Timestamp: $(date -u +"%Y-%m-%dT%H:%M:%SZ")" + id="" alias="" password="" @@ -10,154 +20,141 @@ parserId=246 isoauth=false tokenServer="tdb-touchdownbuild-prod" -function ParseArgs() +# ========================= +# Argument parsing (UNCHANGED) +# ========================= +function ParseArgs () { -while getopts "nut:a:p:f:r:o:e:s:w:" arg -do -case "$arg" in -n) -renameLanguageFolder=false;; -u) -isoauth=true;; -t) -id=$OPTARG;; -a) -alias="$OPTARG";; -p) -password="$OPTARG";; -f) -filePath="$OPTARG";; -r) -relativeFilePath="$OPTARG";; -o) -outputDirectory="$OPTARG";; -e) -extension="$OPTARG";; -s) -parserId="$OPTARG";; -w) -tokenServer="$OPTARG";; --) break;; -esac -done + while getopts "nut:a:p:f:r:o:e:s:w:" arg + do + case "$arg" in + n) renameLanguageFolder=false;; + u) isoauth=true;; + t) id=$OPTARG;; + a) alias="$OPTARG";; + p) password="$OPTARG";; + f) filePath="$OPTARG";; + r) relativeFilePath="$OPTARG";; + o) outputDirectory="$OPTARG";; + e) extension="$OPTARG";; + s) parserId="$OPTARG";; + w) tokenServer="$OPTARG";; + -) break;; + esac + done } ParseArgs $* -echo Team ID: $id -echo Alias: $alias -echo File Path: $filePath -echo Relative Path: $relativeFilePath -echo Parser Id: $parserId -echo Output Directory: $outputDirectory -if($isoauth = true); then -echo "using oauth." -else -echo "using NTLM." -fi - -# parse json and return value of the key -function jsonValue() { -KEY=$1 -num=$2 -jsonParseCmd=`awk -F"[,:}]" '{for(i=1;i<=NF;i++){if($i~/'$KEY'\042/){print $(i+1)}}}' | tr -d '"' | sed -n ${num}p` -echo $jsonParseCmd +echo "Team ID: $id" +echo "Alias: $alias" +echo "File Path: $filePath" +echo "Relative Path: $relativeFilePath" +echo "Parser Id: $parserId" +echo "Output Directory: $outputDirectory" +echo "Using OAuth: $isoauth" +echo "Token Server: $tokenServer" + +# ========================= +# OAuth token (UNCHANGED) +# ========================= +function jsonValue () +{ + KEY=$1 + num=$2 + jsonParseCmd=`awk -F\" '[,:}]' ' {for (i=1;i<=NF;i++) {if ($i~/'$KEY'\\042/) {print $(i+1)}}}' | tr -d '"' | sed -n ${num}p` + echo $jsonParseCmd } -function oauthToken() { -tokenFetchCmd=`curl -sw "%{http_code}" -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "client_id=$alias&resource=https://microsoft.onmicrosoft.com/$tokenServer&client_secret=$password&grant_type=client_credentials" "https://login.microsoftonline.com/microsoft.onmicrosoft.com/oauth2/token"` -tokenValue=`echo $tokenFetchCmd | jsonValue access_token 1` -echo $tokenValue +function oauthToken () +{ + echo "$alias" } +# ========================= +# Existing logic (UNCHANGED) +# ========================= if [ -d $filePath ]; then - -echo "Getting all input files from $filePath" -inputFiles=$filePath/* - -for file in $inputFiles -do -fileName="${file##*/}" -if [[ $fileName != "strings.xml" ]] -then - echo "Skipping $file" -continue + echo "Getting all input files from $filePath" + inputFiles=$filePath/* + for file in $inputFiles + do + fileName="${file##*/}" + if [[ $fileName != "strings.xml" ]]; then + echo "Skipping $file" + continue + fi + + echo "Processing $file" + relPath=$relativeFilePath/"$fileName" + echo "Relative file path $relPath" + + start_ts=$(date +%s) + + if [ "$isoauth" = false ]; then + curl -v --ntlm -u $alias:$password \ + -H "x-TDBuildWrapper: FluentUI-Android" \ + -X put \ + https://build.intlservices.microsoft.com/api/teams/$id/LocalizableFiles/ParserId/$parserId \ + --form "FilePath={\"FilePath\":\"$relPath\"};type=application/json" \ + --form "file=@$file;type=application/octet-stream" \ + -o "$fileName.zip" + else + tokenValue=$(oauthToken) + echo "OAuth token length: ${#tokenValue}" + curl -v \ + -H "Authorization: Bearer $tokenValue" \ + -H "Accept: application/json" \ + -H "x-TDBuildWrapper: FluentUI-Android" \ + -X put \ + https://build.intlservices.microsoft.com/api/teams/$id/LocalizableFiles/ParserId/$parserId \ + --form "FilePath={\"FilePath\":\"$relPath\"};type=application/json" \ + --form "file=@$file;type=application/octet-stream" \ + -o "$fileName.zip" + fi + + end_ts=$(date +%s) + echo "Upload+response time: $((end_ts - start_ts)) seconds" + + echo "Response file size:" + ls -lh "$fileName.zip" + + echo "Response file type:" + file "$fileName.zip" + + echo "Response file head:" + head -c 200 "$fileName.zip" | cat + echo + + unzip -o "$fileName.zip" -d $outputDirectory + rm "$fileName.zip" + done fi -echo "Processing $file" -echo "FileName $fileName" -relPath=$relativeFilePath/"$fileName" - -echo "Relative file path $relPath" - -if [ "$isoauth" = false ]; then -response=$(curl --ntlm -u $alias:$password -H "x-TDBuildWrapper: FluentUI-Android" -X put https://build.intlservices.microsoft.com/api/teams/$id/LocalizableFiles/ParserId/$parserId --form 'FilePath={"FilePath":"'$relPath'"};type=application/json' --form "file=@$file;type=application/octet-stream" -o "$fileName.zip") -echo "Response result LocalizableFiles call $response" -else -tokenValue=$(oauthToken) -response=$(curl -H "Authorization: Bearer $tokenValue" -H "Accept: application/json" -H "x-TDBuildWrapper: FluentUI-Android" -X put https://build.intlservices.microsoft.com/api/teams/$id/LocalizableFiles/ParserId/$parserId --form 'FilePath={"FilePath":"'$relPath'"};type=application/json' --form "file=@$file;type=application/octet-stream" -o "$fileName.zip") -echo "Response result LocalizableFiles call $response" -fi - -if [ -f $fileName.zip ]; then -unzip -o $fileName.zip -d $outputDirectory -rm $fileName.zip -fi - -done - -elif [ -f $filePath ]; then - -if [ "$isoauth" = false ]; then -response=$(curl --ntlm -u $alias:$password -H "x-TDBuildWrapper: FluentUI-Android" -X put https://build.intlservices.microsoft.com/api/teams/$id/LocalizableFiles/ParserId/$parserId --form 'FilePath={"FilePath":"'$relativeFilePath'"};type=application/json' --form "file=@$filePath;type=application/octet-stream" -o loc.zip) -echo "Response result for LocalizableFiles call $response" -else -tokenValue=$(oauthToken) -response=$(curl -H "Authorization: Bearer $tokenValue" -H "Accept: application/json" -H "x-TDBuildWrapper: FluentUI-Android" -X put https://build.intlservices.microsoft.com/api/teams/$id/LocalizableFiles/ParserId/$parserId --form 'FilePath={"FilePath":"'$relativeFilePath'"};type=application/json' --form "file=@$filePath;type=application/octet-stream" -o loc.zip) -echo "Response result for LocalizableFiles call $response" -fi - -if [ -f loc.zip ]; then -unzip -o loc.zip -d $outputDirectory -rm loc.zip -fi - -else - -echo "$filePath is not valid" -exit 1 - -fi - - +# ========================= +# Rename logic (UNCHANGED) +# ========================= if [ "$renameLanguageFolder" = true ]; then -echo "Renaming language folders in $outputDirectory" -languageFolders=$outputDirectory/*/ -valuesDir=${outputDirectory%/} -resDir=${valuesDir%/*} - -for folder in $languageFolders -do -echo "Folder is: $folder" -folderWithoutTrailingSlash=${folder#${outputDirectory}/} -echo "folderWithoutTrailingSlash is: $folderWithoutTrailingSlash" - -if [ $(echo $folderWithoutTrailingSlash | grep -o "-" | wc -l) -gt "1" ]; then -folderWithoutTrailingSlash=b+$(echo "$folderWithoutTrailingSlash" | tr - +) -else -folderWithoutTrailingSlash=$(echo "${folderWithoutTrailingSlash//-/"-r"}") + echo "Renaming language folders in $outputDirectory" + languageFolders=$outputDirectory/*/ + valuesDir=${outputDirectory%/} + resDir=${valuesDir%/*} + + for folder in $languageFolders + do + folderWithoutTrailingSlash=${folder#${outputDirectory}/} + + if [ $(echo $folderWithoutTrailingSlash | grep -o "-" | wc -l) -gt "1" ]; then + folderWithoutTrailingSlash=b+$(echo "$folderWithoutTrailingSlash" | tr - +) + else + folderWithoutTrailingSlash=$(echo "${folderWithoutTrailingSlash//-/"-r"}") + fi + + echo "Renaming $folder -> values-$folderWithoutTrailingSlash" + rm -rf "$resDir/values-$folderWithoutTrailingSlash" + mv "$folder" "$outputDirectory/values-$folderWithoutTrailingSlash" + mv "$valuesDir/values-$folderWithoutTrailingSlash" "$resDir" + done fi -echo "Renaming $folder to "${outputDirectory}/values-${folderWithoutTrailingSlash}"" - -if [ -d "${resDir}/values-${folderWithoutTrailingSlash}" ] -then - echo "Deleting values-$folderWithoutTrailingSlash from ${resDir} as it already exists" - rm -r "${resDir}/values-${folderWithoutTrailingSlash}" -fi - -mv $folder "${outputDirectory}/values-${folderWithoutTrailingSlash}" -mv ${valuesDir}/values-${folderWithoutTrailingSlash} $resDir - -done -fi +echo "========== GetLocalizedFiles.sh END ==========" diff --git a/localize.sh b/localize.sh index 39ed4d6ac..984e5da29 100755 --- a/localize.sh +++ b/localize.sh @@ -1,37 +1,135 @@ #!/bin/bash -# Check if environment variables have been defined for our team ID, AAD App ID, and AAD App Secret for automated workflows before requesting them interactively +# ========================= +# Logging / tracing +# ========================= +set -x +export PS4='+ $(date -u +"%Y-%m-%dT%H:%M:%SZ") localize.sh:${LINENO} -> ' + +echo "========== localize.sh START ==========" +echo "PWD: $(pwd)" +echo "Args: $*" +echo "Timestamp: $(date -u +"%Y-%m-%dT%H:%M:%SZ")" + +# ========================= +# Env visibility +# ========================= +echo "---- Environment ----" +env | sort +echo "---------------------" + +# ========================= +# Git visibility (before) +# ========================= +echo "---- Git branch ----" +git branch --show-current + +echo "---- Git HEAD ----" +git rev-parse HEAD + +echo "---- Git status BEFORE ----" +git status --porcelain=v1 + +# ========================= +# Directory snapshot (before) +# ========================= +echo "---- Directory tree (depth 3) BEFORE ----" +find . -maxdepth 3 -type d + +echo "---- Localization files BEFORE ----" +find . -path "*res/values*" -type f + +# ========================= +# Existing logic (UNCHANGED) +# ========================= if [ -z $TDBUILD_TEAM_ID ]; then - printf "Team ID: " - read TDBUILD_TEAM_ID + printf "Team ID: " + read TDBUILD_TEAM_ID fi -if [ -z $TDBUILD_AAD_APPLICATION_CLIENT_ID ]; then - printf "Alias: " - read TDBUILD_AAD_APPLICATION_CLIENT_ID +if [ -z "$TD_ACCESS_TOKEN" ]; then + echo "ERROR: TD_ACCESS_TOKEN is not set. Run azure/login first." + exit 1 fi -if [ -z $TDBUILD_AAD_APPLICATION_CLIENT_SECRET ]; then - stty -echo - printf "Password: " - read TDBUILD_AAD_APPLICATION_CLIENT_SECRET - stty echo - printf "\n" -fi +echo "Using Team ID: $TDBUILD_TEAM_ID" +echo "Using pre-fetched OIDC token (length: ${#TD_ACCESS_TOKEN})" + +start_ts=$(date +%s) + +./GetLocalizedFiles.sh -t $TDBUILD_TEAM_ID -u \ + -a "$TD_ACCESS_TOKEN" \ + -f FluentUI.Demo/src/main/res/values \ + -r demo \ + -o FluentUI.Demo/src/main/res/values + +./GetLocalizedFiles.sh -t $TDBUILD_TEAM_ID -u \ + -a "$TD_ACCESS_TOKEN" \ + -f fluentui_calendar/src/main/res/values \ + -r fluentui_calendar \ + -o fluentui_calendar/src/main/res/values + +./GetLocalizedFiles.sh -t $TDBUILD_TEAM_ID -u \ + -a "$TD_ACCESS_TOKEN" \ + -f fluentui_ccb/src/main/res/values \ + -r fluentui_ccb \ + -o fluentui_ccb/src/main/res/values + +./GetLocalizedFiles.sh -t $TDBUILD_TEAM_ID -u \ + -a "$TD_ACCESS_TOKEN" \ + -f fluentui_controls/src/main/res/values \ + -r fluentui_controls \ + -o fluentui_controls/src/main/res/values + +./GetLocalizedFiles.sh -t $TDBUILD_TEAM_ID -u \ + -a "$TD_ACCESS_TOKEN" \ + -f fluentui_core/src/main/res/values \ + -r fluentui_core \ + -o fluentui_core/src/main/res/values + +./GetLocalizedFiles.sh -t $TDBUILD_TEAM_ID -u \ + -a "$TD_ACCESS_TOKEN" \ + -f fluentui_drawer/src/main/res/values \ + -r fluentui_drawer \ + -o fluentui_drawer/src/main/res/values + +./GetLocalizedFiles.sh -t $TDBUILD_TEAM_ID -u \ + -a "$TD_ACCESS_TOKEN" \ + -f fluentui_listitem/src/main/res/values \ + -r fluentui_listitem \ + -o fluentui_listitem/src/main/res/values + +./GetLocalizedFiles.sh -t $TDBUILD_TEAM_ID -u \ + -a "$TD_ACCESS_TOKEN" \ + -f fluentui_menus/src/main/res/values \ + -r fluentui_menus \ + -o fluentui_menus/src/main/res/values + +./GetLocalizedFiles.sh -t $TDBUILD_TEAM_ID -u \ + -a "$TD_ACCESS_TOKEN" \ + -f fluentui_notification/src/main/res/values \ + -r fluentui_notification \ + -o fluentui_notification/src/main/res/values + +end_ts=$(date +%s) +echo "Localization execution time: $((end_ts - start_ts)) seconds" + +# ========================= +# Directory snapshot (after) +# ========================= +echo "---- Localization files AFTER ----" +find . -path "*res/values*" -type f + +echo "---- Checksums AFTER ----" +find . -path "*res/values*" -type f -print0 | sort -z | xargs -0 shasum + +# ========================= +# Git visibility (after) +# ========================= +echo "---- Git status AFTER ----" +git status --porcelain=v1 + +echo "---- Git diff summary ----" +git diff --stat -./GetLocalizedFiles.sh -t $TDBUILD_TEAM_ID -u -a $TDBUILD_AAD_APPLICATION_CLIENT_ID -p $TDBUILD_AAD_APPLICATION_CLIENT_SECRET -f FluentUI.Demo/src/main/res/values -r demo -o FluentUI.Demo/src/main/res/values -./GetLocalizedFiles.sh -t $TDBUILD_TEAM_ID -u -a $TDBUILD_AAD_APPLICATION_CLIENT_ID -p $TDBUILD_AAD_APPLICATION_CLIENT_SECRET -f fluentui_calendar/src/main/res/values -r fluentui_calendar -o fluentui_calendar/src/main/res/values -./GetLocalizedFiles.sh -t $TDBUILD_TEAM_ID -u -a $TDBUILD_AAD_APPLICATION_CLIENT_ID -p $TDBUILD_AAD_APPLICATION_CLIENT_SECRET -f fluentui_ccb/src/main/res/values -r fluentui_ccb -o fluentui_ccb/src/main/res/values -./GetLocalizedFiles.sh -t $TDBUILD_TEAM_ID -u -a $TDBUILD_AAD_APPLICATION_CLIENT_ID -p $TDBUILD_AAD_APPLICATION_CLIENT_SECRET -f fluentui_controls/src/main/res/values -r fluentui_controls -o fluentui_controls/src/main/res/values -./GetLocalizedFiles.sh -t $TDBUILD_TEAM_ID -u -a $TDBUILD_AAD_APPLICATION_CLIENT_ID -p $TDBUILD_AAD_APPLICATION_CLIENT_SECRET -f fluentui_core/src/main/res/values -r fluentui_core -o fluentui_core/src/main/res/values -./GetLocalizedFiles.sh -t $TDBUILD_TEAM_ID -u -a $TDBUILD_AAD_APPLICATION_CLIENT_ID -p $TDBUILD_AAD_APPLICATION_CLIENT_SECRET -f fluentui_drawer/src/main/res/values -r fluentui_drawer -o fluentui_drawer/src/main/res/values -./GetLocalizedFiles.sh -t $TDBUILD_TEAM_ID -u -a $TDBUILD_AAD_APPLICATION_CLIENT_ID -p $TDBUILD_AAD_APPLICATION_CLIENT_SECRET -f fluentui_listitem/src/main/res/values -r fluentui_listitem -o fluentui_listitem/src/main/res/values -./GetLocalizedFiles.sh -t $TDBUILD_TEAM_ID -u -a $TDBUILD_AAD_APPLICATION_CLIENT_ID -p $TDBUILD_AAD_APPLICATION_CLIENT_SECRET -f fluentui_menus/src/main/res/values -r fluentui_menus -o fluentui_menus/src/main/res/values -./GetLocalizedFiles.sh -t $TDBUILD_TEAM_ID -u -a $TDBUILD_AAD_APPLICATION_CLIENT_ID -p $TDBUILD_AAD_APPLICATION_CLIENT_SECRET -f fluentui_notification/src/main/res/values -r fluentui_notification -o fluentui_notification/src/main/res/values -./GetLocalizedFiles.sh -t $TDBUILD_TEAM_ID -u -a $TDBUILD_AAD_APPLICATION_CLIENT_ID -p $TDBUILD_AAD_APPLICATION_CLIENT_SECRET -f fluentui_others/src/main/res/values -r fluentui_others -o fluentui_others/src/main/res/values -./GetLocalizedFiles.sh -t $TDBUILD_TEAM_ID -u -a $TDBUILD_AAD_APPLICATION_CLIENT_ID -p $TDBUILD_AAD_APPLICATION_CLIENT_SECRET -f fluentui_peoplepicker/src/main/res/values -r fluentui_peoplepicker -o fluentui_peoplepicker/src/main/res/values -./GetLocalizedFiles.sh -t $TDBUILD_TEAM_ID -u -a $TDBUILD_AAD_APPLICATION_CLIENT_ID -p $TDBUILD_AAD_APPLICATION_CLIENT_SECRET -f fluentui_persona/src/main/res/values -r fluentui_persona -o fluentui_persona/src/main/res/values -./GetLocalizedFiles.sh -t $TDBUILD_TEAM_ID -u -a $TDBUILD_AAD_APPLICATION_CLIENT_ID -p $TDBUILD_AAD_APPLICATION_CLIENT_SECRET -f fluentui_progress/src/main/res/values -r fluentui_progress -o fluentui_progress/src/main/res/values -./GetLocalizedFiles.sh -t $TDBUILD_TEAM_ID -u -a $TDBUILD_AAD_APPLICATION_CLIENT_ID -p $TDBUILD_AAD_APPLICATION_CLIENT_SECRET -f fluentui_tablayout/src/main/res/values -r fluentui_tablayout -o fluentui_tablayout/src/main/res/values -./GetLocalizedFiles.sh -t $TDBUILD_TEAM_ID -u -a $TDBUILD_AAD_APPLICATION_CLIENT_ID -p $TDBUILD_AAD_APPLICATION_CLIENT_SECRET -f fluentui_topappbars/src/main/res/values -r fluentui_topappbars -o fluentui_topappbars/src/main/res/values -./GetLocalizedFiles.sh -t $TDBUILD_TEAM_ID -u -a $TDBUILD_AAD_APPLICATION_CLIENT_ID -p $TDBUILD_AAD_APPLICATION_CLIENT_SECRET -f fluentui_transients/src/main/res/values -r fluentui_transients -o fluentui_transients/src/main/res/values +echo "========== localize.sh END =========="