1#!/usr/bin/env bash 2# 3# Copyright 2020 The ChromiumOS Authors 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6# Script creates partner program or project git repo, gerrit groups, apply ACLs 7# 8# Requirements : 9# 1. Script runner needs to be added to group mdb/chrome-git-admins 10# and gerrit group chromeos-partner-admins 11# 2. Install jq for json file parsing 12# $ sudo apt-get install jq 13# 14# The script can get re-run with out any problems. 15# The apis check if repo and gerrit groups exist. 16# ACLs can be re-applied and gets deduped. 17# Creating local_manifest step will be skipped if one already exist. 18# Update of Manifest-internal will be skipped if the project is already added. 19# Creating already existing project buckets is a noop. 20# 21# TODO(b/152909984): Script should check script runner's env for correct setup 22 23readonly INTERNAL_GERRIT_HOST='https://chrome-internal-review.googlesource.com' 24readonly INTERNAL_GITILES_HOST='https://chrome-internal.googlesource.com' 25readonly CONTENT_TYPE_JSON='Content-Type: application/json' 26readonly PROGRAM_CONFIG_BUCKET_PREFIX='gs://chromeos-device-lifecycle-config-' 27 28# Name of the program 29program='' 30# Name of the project 31project='' 32# Command to run if provided, otherwise run through entire script. 33run='' 34# Path of the repo depending on program and project 35repo_path='' 36# Name used to create repo and gerrit groups 37name='' 38# Parsed project json object 39global_project_obj='' 40# Groups parsed from program config with ACL roles ODM, OEM, or SOC 41committer_groups='' 42# Users parsed from program config with ACL roles ODM, OEM, or SOC 43committer_users='' 44# environment Program.config bucket setting could be prod or dev 45env='prod' 46 47readonly SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 48readonly WORK_DIR=$(mktemp --tmpdir -d programrepoXXXXXX) 49trap "rm -rf ${WORK_DIR}" EXIT 50 51usage() { 52 echo "usage: ./create_partner_repo --program <programName> --project <projectName> --run <command> --env <prod/dev>" 53 echo "--program is required" 54 echo "--project is optional" 55 echo "--run is optional. Supported commands:" 56 echo " acls - apply ACLs to project's committer and access groups" 57 echo " gerritgroups - create and apply default permissions to gerrit groups" 58 echo " localmanifest - create local_manifest.xml for the project repo" 59 echo " manifestinternal - update manifest_internal repo internal_full.xml" 60 echo " createprogrambuckets - create gs buckets for the program" 61 echo " createprojectbuckets - create gs buckets for the project" 62 echo " createprojectbranches - create firmware and factory branches for the project and calls manifestinternal" 63 echo "--env is optional. prod or dev. prod is default" 64} 65 66gob_post() { 67 local body="${1}" 68 local url="${2}" 69 gob-curl -X POST -H "${CONTENT_TYPE_JSON}" -d "${body}" "${url}" 70} 71 72gob_put() { 73 local body="${1}" 74 local url="${2}" 75 gob-curl -X PUT -H "${CONTENT_TYPE_JSON}" -d "${body}" "${url}" 76} 77 78gob_curl() { 79 local url="${1}" 80 local output="${2}" 81 tempfile=$(mktemp) 82 gob-curl --silent --output "${tempfile}" "${url}" 83 84 # Expect a magic prefix as the first line, see 85 # https://gerrit-review.googlesource.com/Documentation/rest-api.html#output. 86 if [[ $(head -1 "${tempfile}") != ')]}'\' ]]; then 87 echo "Unexpected prefix line in JSON response." 88 return 1 89 fi 90 91 tail -n +2 "${tempfile}" > "${output}" 92} 93 94apply_acls_with_static_members() { 95 if [[ -n "${project}" ]]; then 96 local groups=( 97 '"google/'"${project}"'-odm-external@google.com"' 98 '"google/'"${project}"'-oem-external@google.com"' 99 '"google/'"${project}"'-soc-external@google.com"') 100 local url="${INTERNAL_GERRIT_HOST}/a/groups/chromeos-partner-${project}-committers/groups" 101 102 for group in "${groups[@]}"; do 103 echo "## adding group ${group}" 104 local body='{"groups": ['"${group}"']}' 105 gob_post "${body}" "${url}" 106 done 107 fi 108 109 apply_default_access 110} 111 112parse_program_config() { 113 local config_file="${PROGRAM_CONFIG_BUCKET_PREFIX}${env}/${program}_program_config.json" 114 115 if [[ -z "${project}" ]]; then 116 global_project_obj=$(gsutil cat "${config_file}" | \ 117 jq 'select(.name=="'$program'")') 118 else 119 global_project_obj=$(gsutil cat "${config_file}" | \ 120 jq 'select(.name=="'$program'") | .deviceProjects[] | select(.googleCodeName=="'$project'")') 121 fi 122 123 if [[ -z "${global_project_obj}" ]]; then 124 if [[ -z "${project}" ]]; then 125 echo "Program not found in config. ACLs are not parsed." 126 else 127 echo "Project not found in config. ACLs are not parsed." 128 fi 129 return; 130 fi 131 132 local committer_acls 133 committer_acls=$(echo "${global_project_obj}" | \ 134 jq '.acls[]? | select(.role=="DEVICE_OEM"), select(.role=="DEVICE_ODM"), select(.role=="PLATFORM_SUPPLIER")') 135 136 # groups should end with google.com and should be prefixed with "google/" 137 # Can't add non google.com entries. 138 committer_groups=$(echo "${committer_acls}" | \ 139 jq -r '[.groupEmails] | add | .[]? | select( . | contains("google.com"))'); 140 echo "committer groups: ${committer_groups}" 141 142 committer_users=$(echo "${committer_acls}" | \ 143 jq -r '[.userEmails] | add | .[]?'); 144 echo "committer users: ${committer_users}" 145} 146 147# Gerrit request fails if any of the group or user in list is invalid. Add serially 148apply_acls_from_config() { 149 if [[ -n "${project}" ]]; then 150 # Add groups 151 if [[ -n "${committer_groups}" ]]; then 152 local url="${INTERNAL_GERRIT_HOST}/a/groups/chromeos-partner-${name}-committers/groups" 153 for group in ${committer_groups}; do 154 local json='{"groups": ["google/'"${group}"'"]}' 155 echo "Applying ACL : ${json} to ${url}" 156 gob_post "${json}" "${url}" 157 done 158 else 159 echo "No groups found in program config, no groups added." 160 fi 161 162 # Add users 163 if [[ -n "${committer_users}" ]]; then 164 local url="${INTERNAL_GERRIT_HOST}/a/groups/chromeos-partner-${name}-committers/members" 165 for user in ${committer_users}; do 166 local json='{"members": ["'"${user}"'"]}' 167 echo "Applying ACL : ${json} to ${url}" 168 gob_post "${json}" "${url}" 169 done 170 else 171 echo "No users found in program config, no users added." 172 fi 173 fi 174 175 apply_default_access 176} 177 178apply_default_access() { 179 echo -e "\n## Add default required reader access ${INTERNAL_GERRIT_HOST}/${repo_path//%2f/\/}" 180 gob-ctl acl "${INTERNAL_GERRIT_HOST}/${repo_path//%2f/\/}" \ 181 -reader "email/chromeos-commit-bot@chromium.org" 182 183 gob-ctl acl "${INTERNAL_GERRIT_HOST}/${repo_path//%2f/\/}" \ 184 -reader "mdbgroup/chromeos-ci-oncalls" 185 186 gob-ctl acl "${INTERNAL_GERRIT_HOST}/${repo_path//%2f/\/}" \ 187 -reader "mdbgroup/chromeos-ci-server" 188 189 echo -e "## Project's access group gets added to the program's access group" 190 echo "If project is a reference-board, its committer groups gets added to chromeos-partner-${program}-committers" 191 if [[ -n "${project}" ]] && [[ "${project}" != "${program}" ]]; then 192 gob-curl -X PUT "${INTERNAL_GERRIT_HOST}/a/groups/chromeos-partner-${program}-access/groups/chromeos-partner-${project}-access" 193 194 if [[ -n "${global_project_obj}" ]]; then 195 local referenceBoardName 196 referenceBoardName=$(echo "${global_project_obj}" | jq '.referenceBoardName') 197 if [[ "referenceBoardName" == "${project}" ]]; then 198 gob-curl -X PUT "${INTERNAL_GERRIT_HOST}/a/groups/chromeos-partner-${program}-committers/groups/chromeos-partner-${project}-committers" 199 fi 200 fi 201 fi 202} 203 204create_gerrit_groups() { 205 #Create an admin gerrit group" 206 echo -e "## Create an admin gerrit group" 207 gob_put \ 208 '{"owner": "chromeos-partner-admins"}' \ 209 "${INTERNAL_GERRIT_HOST}/a/groups/chromeos-partner-${name}-admin" 210 211 gob_put \ 212 '{"owner": "chromeos-partner-admins"}' \ 213 "${INTERNAL_GERRIT_HOST}/a/groups/chromeos-partner-${name}-admin/owner" 214 215 # Create committers gerrit group" 216 echo -e "\n## Create committers gerrit group" 217 gob_put \ 218 '{"owner": "chromeos-partner-'"${name}"'-admin", "visible_to_all": "true"}' \ 219 "${INTERNAL_GERRIT_HOST}/a/groups/chromeos-partner-${name}-committers" 220 221 gob_put \ 222 '{"owner": "chromeos-partner-'"${name}"'-admin"}' \ 223 "${INTERNAL_GERRIT_HOST}/a/groups/chromeos-partner-${name}-committers/owner" 224 225 # Create access gerrit group" 226 echo -e "\n## Create access gerrit group" 227 gob_put \ 228 '{"owner": "chromeos-partner-'"${name}"'-admin", "visible_to_all": "true"}' \ 229 "${INTERNAL_GERRIT_HOST}/a/groups/chromeos-partner-${name}-access" 230 231 gob_put \ 232 '{"owner": "chromeos-partner-'"${name}"'-admin"}' \ 233 "${INTERNAL_GERRIT_HOST}/a/groups/chromeos-partner-${name}-access/owner" 234 235 echo -e "\n## Add committer to access group" 236 gob-curl -X PUT "${INTERNAL_GERRIT_HOST}/a/groups/chromeos-partner-${name}-access/groups/chromeos-partner-${name}-committers" 237 if [[ -n "${project}" ]] && [[ "${project}" != "${program}" ]]; then 238 echo -e "\n## Add "chromeos-partner-"$project"-access" as an included group in the program's access group" 239 gob-curl -X PUT "${INTERNAL_GERRIT_HOST}/a/groups/chromeos-partner-${program}-access/groups/chromeos-partner-${project}-access" 240 fi 241 242 # Add chromeos-partner-${name}-access group to chromeos-allpartners" 243 gob-curl -X PUT "${INTERNAL_GERRIT_HOST}/a/groups/chromeos-allpartners/groups/chromeos-partner-${name}-access" 244 245 # Add the chromeos-partner-${name}-access group as an included group of chromeos-partner-project-cheets-access" 246 gob-curl -X PUT "${INTERNAL_GERRIT_HOST}/a/groups/chromeos-partner-project-cheets-access/groups/chromeos-partner-${name}-access" 247 248 # Permissions at /google3/third_party/java_src/gerritcodereview/gerrit/java/com/google/gerrit/common/data/Permission.java 249 #Step 9: 250 echo -e "\n## Set up access permission" 251 refs_acls=( 252 '{"add": {"refs/*": {"permissions": {"forgeAuthor": {"rules": {"chromeos-partner-'"$name"'-committers": {"action": "ALLOW"}}}}}}}' 253 '{"add": {"refs/*": {"permissions": {"label-Code-Review": {"rules": {"chromeos-partner-'"$name"'-committers": {"action": "ALLOW", "min": -1, "max": 1}}}}}}}' 254 '{"add": {"refs/*": {"permissions": {"label-Commit-Queue": {"rules": {"chromeos-partner-'"$name"'-committers": {"action": "ALLOW", "min": 0, "max": 1}}}}}}}' 255 '{"add": {"refs/*": {"permissions": {"label-Verified": {"rules": {"chromeos-partner-'"$name"'-committers": {"action": "ALLOW", "min": -1, "max": 1}}}}}}}' 256 '{"add": {"refs/*": {"permissions": {"read": {"rules": {"chromeos-partner-'"$name"'-access": {"action": "ALLOW"}}}}}}}' 257 '{"add": {"refs/for/*": {"permissions": {"create": {"rules": {"chromeos-partner-'"$name"'-committers": {"action": "ALLOW"}}}}}}}' 258 '{"add": {"refs/for/*": {"permissions": {"push": {"rules": {"chromeos-partner-'"$name"'-committers": {"action": "ALLOW"}}}}}}}' 259 '{"add": {"refs/for/refs/*": {"permissions": {"pushMerge": {"rules": {"chromeos-partner-'"$name"'-committers": {"action": "ALLOW"}}}}}}}' 260 '{"add": {"refs/for/refs/*": {"permissions": {"push": {"rules": {"chromeos-partner-'"$name"'-committers": {"action": "ALLOW"}}}}}}}') 261 262 for ref_acl in "${refs_acls[@]}"; do 263 echo "## adding ${ref_acl}" 264 gob_post "${ref_acl}" "${INTERNAL_GERRIT_HOST}/a/projects/${repo_path}/access" 265 done 266} 267 268# Update manifest once when all the projects have been created 269# TODO(b/153167560): Determine how to update_manifest once for all projects. 270# 271# Args: 272# branch: Optional, the branch to update the manifest on. 273update_manifest_internal() { 274 local branch="${1}" 275 cd "${WORK_DIR}" 276 if [[ -n "${branch}" ]]; then 277 echo -e "\n## Add ${program}-${name} to manifest-internal/internal_full.xml (branch ${branch})" 278 git clone "https://chrome-internal.googlesource.com/chromeos/manifest-internal" --branch "${branch}" 279 else 280 echo -e "\n## Add ${program}-${name} to manifest-internal/internal_full.xml" 281 git clone "https://chrome-internal.googlesource.com/chromeos/manifest-internal" 282 fi 283 284 cd manifest-internal 285 286 #Read internal_full, add only if project is not already there 287 if [[ -z "${project}" ]]; then 288 if [[ -n $(grep 'name="chromeos/program/'"${program}"'"' internal_full.xml) ]]; then 289 echo "Program already exist in internal_full.xml. Do nothing." 290 return 0 291 fi 292 else 293 if [[ -n $(grep 'name="chromeos/project/'"${program}"'/'"${name}"'"' internal_full.xml) ]]; then 294 echo "Project already exist in internal_full.xml. Do nothing." 295 return 0 296 fi 297 fi 298 299 if [[ -z "${project}" ]]; then 300 local project_xml='<project remote="cros-internal"\n ' 301 project_xml+=' path="src\/program\/'"${name}"'"\n ' 302 project_xml+=' name="chromeos\/program\/'"${name}"'"\n ' 303 project_xml+=' groups="partner-config" \/>\n' 304 project_xml+='<\/manifest>' 305 else 306 local project_xml='<project remote="cros-internal"\n ' 307 project_xml+=' path="src\/project\/'"${program}"'\/'"${name}"'"\n ' 308 project_xml+=' groups="partner-config"\n ' 309 project_xml+=' name="chromeos\/project\/'"${program}"'\/'"${name}"'" \/>\n' 310 project_xml+='<\/manifest>' 311 fi 312 313 sed -i "s/<\/manifest>/${project_xml}/" internal_full.xml 314 git add internal_full.xml 315 git commit -m "Add to the internal manifest. ${program}/${name}" 316 git cl upload -r "chromeos-continuous-integration-team@google.com" --force 317} 318 319create_local_manifest() { 320 if [[ -n "${project}" ]]; then 321 local source_manifest="${SCRIPT_DIR}/manifests/${program}_local_manifest.xml" 322 if [[ ! -f "${source_manifest}" ]]; then 323 echo "Source manifest ${source_manifest} does not exist." 324 echo "Skipping local manifest creation." 325 return 326 fi 327 328 echo -e "\n## Add local_manifest.xml at ${INTERNAL_GITILES_HOST}/${repo_path//%2f/\/}" 329 cd "$WORK_DIR" 330 git clone "${INTERNAL_GITILES_HOST}/${repo_path//%2f/\/}" 331 cd "${name}" 332 if [[ -e local_manifest.xml ]]; then 333 echo "local_manifest.xml already exist in repo. Do nothing." 334 return 0 335 fi 336 337 git checkout -b "${program}"-"${name}" 338 cp "${source_manifest}" local_manifest.xml 339 git add local_manifest.xml 340 git commit -m "Add local_manifest.xml for project. ${program}/${project}" 341 git cl upload -r "chromeos-continuous-integration-team@google.com" --force 342 fi 343} 344 345create_git_repo() { 346 echo -e "\n## Create internal repo at ${INTERNAL_GERRIT_HOST}/a/projects/${repo_path}" 347 repo_description='{"description": "Partner Chromeos internal project repo", 348 "submit_type":"INHERIT", 349 "owners": ["mdb/chrome-git-admins"], 350 "create_empty_commit": "true", 351 "parent": "chromeos", 352 "branches": ["main"]}' 353 gob_put "${repo_description}" "${INTERNAL_GERRIT_HOST}/a/projects/${repo_path}" 354} 355 356try_set_group_viewer() { 357 local group="${1}" 358 local bucket="${2}" 359 if ! gsutil iam ch "group:${group}:objectViewer" "${bucket}"; then 360 echo "Skipping setting objectViewer for ${group} on ${bucket}." >&2 361 else 362 echo "Set objectViewer for ${group} on ${bucket}." 363 fi 364} 365 366try_set_user_viewer() { 367 local user="${1}" 368 local bucket="${2}" 369 if ! gsutil iam ch "user:${user}:objectViewer" "${bucket}"; then 370 echo "Skipping setting objectViewer for ${user} on ${bucket}." >&2 371 else 372 echo "Set objectViewer for ${user} on ${bucket}." 373 fi 374} 375 376create_project_buckets() { 377 # setup() guaranteed that we have a $program. 378 if [[ -n "${project}" ]]; then 379 gcloud config set project chromeos-partner-devices 380 local bucket="gs://chromeos-${program}-${project}" 381 gsutil mb "${bucket}" 382 gsutil iam ch serviceAccount:chromeos-ci-prod@chromeos-bot.iam.gserviceaccount.com:objectAdmin "${bucket}" 383 gsutil iam ch user:chromeos-ci-server@prod.google.com:objectViewer "${bucket}" 384 for id in ${committer_groups}; do 385 try_set_group_viewer "${id}" "${bucket}" 386 done 387 for id in ${committer_users}; do 388 try_set_user_viewer "${id}" "${bucket}" 389 done 390 else 391 echo "No project specified, skipping creation of project bucket." 392 fi 393} 394 395create_program_buckets() { 396 # setup() guaranteed that we have a $program. 397 gcloud config set project chromeos-partner-devices 398 local bucket="gs://chromeos-${program}" 399 gsutil mb "${bucket}" 400 gsutil iam ch serviceAccount:chromeos-ci-prod@chromeos-bot.iam.gserviceaccount.com:objectAdmin "${bucket}" 401 gsutil iam ch user:chromeos-ci-server@prod.google.com:objectViewer "${bucket}" 402 for id in ${committer_groups}; do 403 try_set_group_viewer "${id}" "${bucket}" 404 done 405 for id in ${committer_users}; do 406 try_set_user_viewer "${id}" "${bucket}" 407 done 408} 409 410create_project_branches() { 411 # setup() guaranteed that we have a $program. 412 if [[ -z "${project}" ]]; then 413 echo "No project specified, skipping creation of project branches." 414 return 415 fi 416 417 local branch_info 418 branch_info=$(mktemp) 419 local branch_types=( 'firmware' 'factory') 420 421 # Search for firmware and factory branches on manifest-internal based on 422 # regex. 423 gob_curl "${INTERNAL_GERRIT_HOST}/a/projects/chromeos%2fmanifest-internal/branches" "${branch_info}" 424 425 for type in "${branch_types[@]}"; do 426 local branches 427 mapfile -t branches < <(jq --raw-output ".[] | select(.ref|test(\"^refs/heads/${type}-${program}-[1-9]+.B$\")) | .ref" < "${branch_info}") 428 429 if [[ "${#branches[@]}" = 1 ]]; then 430 # Strip refs/heads/ for the Gerrit API call. 431 local branch="${branches[0]#refs/heads/}" 432 echo "Manifest branch ${branch} found, creating corresponding project branch." 433 gob_put '{}' "${INTERNAL_GERRIT_HOST}/a/projects/${repo_path}/branches/${branch}" 434 435 update_manifest_internal "${branch}" 436 elif [[ "${#branches[@]}" -ge 2 ]]; then 437 echo "More than one manifest branch found: ${branches[*]}. Please create corresponding project branch manually." 438 else 439 echo "No existing ${type} branch found, skipping creation." 440 fi 441 done 442} 443 444setup() { 445 # Setup checks for program and project defined 446 if [[ -z "${program}" ]]; then 447 usage 448 exit 1 449 elif [[ -z "${project}" ]]; then 450 name=${program} 451 repo_path="chromeos/program/${program}" 452 else 453 name=${project} 454 repo_path="chromeos/project/${program}/${project}" 455 fi 456 repo_path=${repo_path//\//%2f} 457 #set -x 458} 459 460run_command() { 461 # Script will run commands 462 if [[ -n "$run" ]]; then 463 if [[ "$run" == "acls" ]]; then 464 parse_program_config 465 apply_acls_from_config 466 elif [[ "$run" == "gerritgroups" ]]; then 467 create_gerrit_groups 468 elif [[ "$run" == "localmanifest" ]]; then 469 create_local_manifest 470 elif [[ "$run" == "manifestinternal" ]]; then 471 update_manifest_internal 472 elif [[ "$run" == "createprojectbuckets" ]]; then 473 parse_program_config 474 create_project_buckets 475 elif [[ "$run" == "createprogrambuckets" ]]; then 476 parse_program_config 477 create_program_buckets 478 elif [[ "$run" == "createprojectbranches" ]]; then 479 create_project_branches 480 else 481 echo 'Unknown --run command' 482 usage 483 fi 484 exit 0 485 fi 486} 487 488if ! prodcertstatus; then 489 # Some of the commands require LOAS credentials. For example, without LOAS 490 # credentials, the gob-ctl to set readers on repos will fail. 491 echo "LOAS credentials missing. Please log in with prodaccess." >&2 492 exit 1 493fi 494 495while [[ "$1" != "" ]]; do 496 case $1 in 497 --program) 498 shift 499 program=$1 500 ;; 501 --project) 502 shift 503 project=$1 504 ;; 505 --env) 506 shift 507 env=$1 508 ;; 509 --run) 510 shift 511 run=$1 512 ;; 513 -h | --help) 514 usage 515 exit 516 ;; 517 *) 518 echo "Unknown option $1" 519 usage 520 exit 521 ;; 522 esac 523 shift 524done 525 526setup 527run_command 528# If fall through, run script from AtoZ 529#Step 1: 530create_git_repo 531#Step 2: 532create_gerrit_groups 533#Step 3: 534parse_program_config 535apply_acls_from_config 536#Step 4: 537create_local_manifest 538#Step 5: 539create_program_buckets 540#Step 6: 541create_project_buckets 542#Step 7: 543create_project_branches 544echo '## Script completed' 545