blob: 554b81c09d886086fbb39816b8a4b7c43daa33fb [file] [log] [blame]
Shuqian Zhaoae2d0782016-11-15 16:58:47 -08001#!/usr/bin/python
2
3# Copyright (c) 2016 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Module to automate the process of deploying to production.
8
9Example usage of this script:
10 1. Update both autotest and chromite to the lastest commit that has passed
11 the test instance.
12 $ ./site_utils/automated_deploy.py
13 2. Skip updating a repo, e.g. autotest
14 $ ./site_utils/automated_deploy.py --skip_autotest
15 3. Update a given repo to a specific commit
16 $ ./site_utils/automated_deploy.py --autotest_hash='1234'
17"""
18
19import argparse
20import os
21import re
22import sys
23import subprocess
24
25import common
26from autotest_lib.client.common_lib import revision_control
27from autotest_lib.site_utils.lib import infra
28
29AUTOTEST_DIR = common.autotest_dir
30GIT_URL = {'autotest':
31 'https://chromium.googlesource.com/chromiumos/third_party/autotest',
32 'chromite':
33 'https://chromium.googlesource.com/chromiumos/chromite'}
34PROD_BRANCH = 'prod'
35MASTER_AFE = 'cautotest'
Shuqian Zhaoa482c4a2016-11-21 18:49:41 -080036NOTIFY_GROUP = 'chromeos-infra-discuss@google.com'
Shuqian Zhaoae2d0782016-11-15 16:58:47 -080037
Daniel Erat590a4992018-01-29 15:57:58 -080038# CIPD packages whose prod refs should be updated.
39_CIPD_PACKAGES = (
40 'chromiumos/infra/lucifer',
Daniel Erat590a4992018-01-29 15:57:58 -080041)
42
Shuqian Zhaoae2d0782016-11-15 16:58:47 -080043
44class AutoDeployException(Exception):
45 """Raised when any deploy step fails."""
46
47
48def parse_arguments():
49 """Parse command line arguments.
50
51 @returns An argparse.Namespace populated with argument values.
52 """
53 parser = argparse.ArgumentParser(
54 description=('Command to update prod branch for autotest, chromite '
55 'repos. Then deploy new changes to all lab servers.'))
56 parser.add_argument('--skip_autotest', action='store_true', default=False,
57 help='Skip updating autotest prod branch. Default is False.')
58 parser.add_argument('--skip_chromite', action='store_true', default=False,
59 help='Skip updating chromite prod branch. Default is False.')
Shuqian Zhao67ee3b42017-12-07 11:12:18 -080060 parser.add_argument('--force_update', action='store_true', default=False,
61 help=('Force a deployment without updating both autotest and '
62 'chromite prod branch'))
Shuqian Zhaoae2d0782016-11-15 16:58:47 -080063 parser.add_argument('--autotest_hash', type=str, default=None,
64 help='Update autotest prod branch to the given hash. If it is not'
65 ' specified, autotest prod branch will be rebased to '
66 'prod-next branch, which is the latest commit that has '
67 'passed our test instance.')
68 parser.add_argument('--chromite_hash', type=str, default=None,
69 help='Same as autotest_hash option.')
70
71 results = parser.parse_args(sys.argv[1:])
72
73 # Verify the validity of the options.
74 if ((results.skip_autotest and results.autotest_hash) or
75 (results.skip_chromite and results.chromite_hash)):
76 parser.print_help()
77 print 'Cannot specify skip_* and *_hash options at the same time.'
78 sys.exit(1)
Shuqian Zhao67ee3b42017-12-07 11:12:18 -080079 if results.force_update:
80 results.skip_autotest = True
81 results.skip_chromite = True
Shuqian Zhaoae2d0782016-11-15 16:58:47 -080082 return results
83
84
85def clone_prod_branch(repo):
86 """Method to clone the prod branch for a given repo under /tmp/ dir.
87
88 @param repo: Name of the git repo to be cloned.
89
90 @returns path to the cloned repo.
91 @raises subprocess.CalledProcessError on a command failure.
92 @raised revision_control.GitCloneError when git clone fails.
93 """
94 repo_dir = '/tmp/%s' % repo
95 print 'Cloning %s prod branch under %s' % (repo, repo_dir)
96 if os.path.exists(repo_dir):
97 infra.local_runner('rm -rf %s' % repo_dir)
98 git_repo = revision_control.GitRepo(repo_dir, GIT_URL[repo])
99 git_repo.clone(remote_branch=PROD_BRANCH)
100 print 'Successfully cloned %s prod branch' % repo
101 return repo_dir
102
103
104def update_prod_branch(repo, repo_dir, hash_to_rebase):
105 """Method to update the prod branch of the given repo to the given hash.
106
107 @param repo: Name of the git repo to be updated.
108 @param repo_dir: path to the cloned repo.
109 @param hash_to_rebase: Hash to rebase the prod branch to. If it is None,
110 prod branch will rebase to prod-next branch.
111
Shuqian Zhao673519b2017-05-05 15:13:25 -0700112 @returns the range of the pushed commits as a string. E.g 123...345. If the
113 prod branch is already up-to-date, return None.
Shuqian Zhaoae2d0782016-11-15 16:58:47 -0800114 @raises subprocess.CalledProcessError on a command failure.
115 """
116 with infra.chdir(repo_dir):
Shuqian Zhaoa482c4a2016-11-21 18:49:41 -0800117 print 'Updating %s prod branch.' % repo
118 rebase_to = hash_to_rebase if hash_to_rebase else 'origin/prod-next'
Shuqian Zhao673519b2017-05-05 15:13:25 -0700119 # Check whether prod branch is already up-to-date, which means there is
120 # no changes since last push.
121 print 'Detecting new changes since last push...'
122 diff = infra.local_runner('git log prod..%s --oneline' % rebase_to,
123 stream_output=True)
124 if diff:
125 print 'Find new changes, will update prod branch...'
126 infra.local_runner('git rebase %s prod' % rebase_to,
127 stream_output=True)
128 result = infra.local_runner('git push origin prod',
129 stream_output=True)
130 print 'Successfully pushed %s prod branch!\n' % repo
Shuqian Zhaoae2d0782016-11-15 16:58:47 -0800131
Shuqian Zhao673519b2017-05-05 15:13:25 -0700132 # Get the pushed commit range, which is used to get pushed commits
133 # using git log E.g. 123..456, then run git log --oneline 123..456.
134 grep = re.search('(\w)*\.\.(\w)*', result)
Shuqian Zhaoae2d0782016-11-15 16:58:47 -0800135
Shuqian Zhao673519b2017-05-05 15:13:25 -0700136 if not grep:
137 raise AutoDeployException(
138 'Fail to get pushed commits for repo %s from git log: %s' %
139 (repo, result))
140 return grep.group(0)
141 else:
142 print 'No new %s changes found since last push.' % repo
143 return None
Shuqian Zhaoae2d0782016-11-15 16:58:47 -0800144
145
146def get_pushed_commits(repo, repo_dir, pushed_commits_range):
147 """Method to get the pushed commits.
148
149 @param repo: Name of the updated git repo.
150 @param repo_dir: path to the cloned repo.
151 @param pushed_commits_range: The range of the pushed commits. E.g 123...345
152 @return: the commits that are pushed to prod branch. The format likes this:
153 "git log --oneline A...B | grep autotest
154 A xxxx
155 B xxxx"
156 @raises subprocess.CalledProcessError on a command failure.
157 """
Shuqian Zhaoa482c4a2016-11-21 18:49:41 -0800158 print 'Getting pushed CLs for %s repo.' % repo
Shuqian Zhao673519b2017-05-05 15:13:25 -0700159 if not pushed_commits_range:
160 return '\n%s:\nNo new changes since last push.' % repo
161
Shuqian Zhaoae2d0782016-11-15 16:58:47 -0800162 with infra.chdir(repo_dir):
163 get_commits_cmd = 'git log --oneline %s' % pushed_commits_range
xixuan6d782dc2017-06-21 18:08:48 -0700164
165 pushed_commits = infra.local_runner(
166 get_commits_cmd, stream_output=True)
Shuqian Zhaoae2d0782016-11-15 16:58:47 -0800167 if repo == 'autotest':
xixuan6d782dc2017-06-21 18:08:48 -0700168 autotest_commits = ''
169 for cl in pushed_commits.splitlines():
170 if 'autotest' in cl:
171 autotest_commits += '%s\n' % cl
172
173 pushed_commits = autotest_commits
174
Shuqian Zhaoa482c4a2016-11-21 18:49:41 -0800175 print 'Successfully got pushed CLs for %s repo!\n' % repo
Aviv Keshet6dd1c3d2017-09-26 17:46:49 -0700176 displayed_cmd = get_commits_cmd
177 if repo == 'autotest':
178 displayed_cmd += ' | grep autotest'
179 return '\n%s:\n%s\n%s\n' % (repo, displayed_cmd, pushed_commits)
Shuqian Zhaoae2d0782016-11-15 16:58:47 -0800180
181
182def kick_off_deploy():
183 """Method to kick off deploy script to deploy changes to lab servers.
184
185 @raises subprocess.CalledProcessError on a repo command failure.
186 """
187 print 'Start deploying changes to all lab servers...'
188 with infra.chdir(AUTOTEST_DIR):
Shuqian Zhaoae2d0782016-11-15 16:58:47 -0800189 # Then kick off the deploy script.
Aviv Keshetfc59c142017-10-31 09:27:57 -0700190 deploy_cmd = ('runlocalssh ./site_utils/deploy_server.py -x --afe=%s' %
Shuqian Zhaoae2d0782016-11-15 16:58:47 -0800191 MASTER_AFE)
Shuqian Zhao6ad127a2017-05-22 22:57:19 +0000192 infra.local_runner(deploy_cmd, stream_output=True)
Aviv Keshetfc59c142017-10-31 09:27:57 -0700193 print 'Successfully deployed changes to all lab servers.'
Shuqian Zhaoae2d0782016-11-15 16:58:47 -0800194
195
196def main(args):
197 """Main entry"""
198 options = parse_arguments()
199 repos = dict()
200 if not options.skip_autotest:
201 repos.update({'autotest': options.autotest_hash})
202 if not options.skip_chromite:
203 repos.update({'chromite': options.chromite_hash})
204
Daniel Erat590a4992018-01-29 15:57:58 -0800205 print 'Moving CIPD prod refs to prod-next'
206 for pkg in _CIPD_PACKAGES:
207 subprocess.check_call(['cipd', 'set-ref', pkg, '-version', 'prod-next',
208 '-ref', 'prod'])
Shuqian Zhaoae2d0782016-11-15 16:58:47 -0800209 try:
210 # update_log saves the git log of the updated repo.
211 update_log = ''
212 for repo, hash_to_rebase in repos.iteritems():
213 repo_dir = clone_prod_branch(repo)
214 push_commits_range = update_prod_branch(
215 repo, repo_dir, hash_to_rebase)
216 update_log += get_pushed_commits(repo, repo_dir, push_commits_range)
217
218 kick_off_deploy()
219 except revision_control.GitCloneError as e:
220 print 'Fail to clone prod branch. Error:\n%s\n' % e
221 raise
222 except subprocess.CalledProcessError as e:
Shuqian Zhao1c3f2462017-02-02 17:21:42 -0800223 print ('Deploy fails when running a subprocess cmd :\n%s\n'
224 'Below is the push log:\n%s\n' % (e.output, update_log))
Shuqian Zhaoae2d0782016-11-15 16:58:47 -0800225 raise
226 except Exception as e:
Shuqian Zhao1c3f2462017-02-02 17:21:42 -0800227 print 'Deploy fails with error:\n%s\nPush log:\n%s\n' % (e, update_log)
Shuqian Zhaoae2d0782016-11-15 16:58:47 -0800228 raise
229
230 # When deploy succeeds, print the update_log.
231 print ('Deploy succeeds!!! Below is the push log of the updated repo:\n%s'
Shuqian Zhaoa482c4a2016-11-21 18:49:41 -0800232 'Please email this to %s.'% (update_log, NOTIFY_GROUP))
Shuqian Zhaoae2d0782016-11-15 16:58:47 -0800233
234
235if __name__ == '__main__':
236 sys.exit(main(sys.argv))