• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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