From 6fe3e6f1426a559bc7576498a6957dd385ff99f9 Mon Sep 17 00:00:00 2001
From: Pavel Korshunov <pavel.korshunov@idiap.ch>
Date: Thu, 29 Mar 2018 12:12:22 +0200
Subject: [PATCH] added options to resume release, single package release, and
 dry-run

---
 release/release_bob.py | 118 +++++++++++++++++++++++++++--------------
 1 file changed, 77 insertions(+), 41 deletions(-)

diff --git a/release/release_bob.py b/release/release_bob.py
index a5dc8fe..e7a1037 100755
--- a/release/release_bob.py
+++ b/release/release_bob.py
@@ -1,23 +1,32 @@
 #!/usr/bin/env python
 
 """
-By using changelog file as an input (can be generated with 'generate_changelog.py' script), this script goes through all packages in changelog file (in order listed), tags them correctly as per the file, an releases them one by one. This script uses python-gitlab package for accessing GitLab's API.
+By using changelog file as an input (can be generated with 'generate_changelog.py' script),
+this script goes through all packages in changelog file (in order listed), tags them correctly
+as per the file, and releases them one by one. A script can also be used to release a single package.
+This script uses python-gitlab package for accessing GitLab's API.
 
 Usage:
-  {0} [-v...] [options] [--] <private_token>
-  {0} -h | --help
-  {0} --version
+    {0} [-v...] [options] [--] <private_token>
+    {0} -h | --help
+    {0} --version
 
 Arguments:
-  <private_token>  Private token used to access GitLab.
+    <private_token>  Private token used to access GitLab.
   
 Options:
-  -h --help                     Show this screen.
-  --version                     Show version.
-  -c, --changelog-file STR      A changelog file with all packages to release with their tags, listed in order.
-                                [default: changelog_since_last_release.rst].
-  -g, --group-name STR          Group name where we are assuming that all packages are located.
-                                [default: bob].
+    -h --help                     Show this screen.
+    --version                     Show version.
+    -c, --changelog-file STR      A changelog file with all packages to release with their tags, listed in order.
+                                  [default: changelog_since_last_release.rst].
+    -g, --group-name STR          Group name where we are assuming that all packages are located.
+                                  [default: bob].
+    -p, --package STR             If the name of a package is provided, then this package will be found
+                                  in the changelog file and the release will resume from it (if option --resume is set)
+                                  or only this package will be released.
+    -r, --resume                  The overall release will resume from the provided package name.
+    -q, --dry-run                 Only print the actions, but do not execute them.
+
 """
 
 import sys
@@ -97,16 +106,17 @@ def get_parsed_tag(gitpkg, tag):
     raise ValueError('Cannot parse changelog tag {0} of the package {1}'.format(tag, gitpkg.name))
 
 
-def update_tag_comments(gitpkg, tag_name, tag_comments_list):
+def update_tag_comments(gitpkg, tag_name, tag_comments_list, dry_run=False):
     # get tag and update its description
     tag = gitpkg.tags.get(tag_name)
-    print('package {0}, tag {1}, updating comments with:'.format(gitpkg.name, tag.name))
+    print('Found tag {1}, updating its comments with:'.format(gitpkg.name, tag.name))
     print(tag_comments_list)
-    tag.set_release_description('\n'.join(tag_comments_list))
+    if not dry_run:
+        tag.set_release_description('\n'.join(tag_comments_list))
     return tag
 
 
-def commit_files(gitpkg, files_list, message='Updated files'):
+def commit_files(gitpkg, files_list, message='Updated files', dry_run=False):
     data = {
         'branch': 'master',  # v4
         'commit_message': message,
@@ -120,11 +130,12 @@ def commit_files(gitpkg, files_list, message='Updated files'):
         update_action['content'] = files_list[filename]
         data['actions'].append(update_action)
 
-    print("Committing changes")
-    gitpkg.commits.create(data)
+    print("Committing changes in files: {0}".format(str(files_list.keys())))
+    if not dry_run:
+        gitpkg.commits.create(data)
 
 
-def just_build_package(gitpkg):
+def just_build_package(gitpkg, dry_run=False):
     # we assume the last pipeline is with commit [skip ci]
     # so, we take the pipeline that can be re-built, which the previous to the last one
     last_pipeline = gitpkg.pipelines.list(per_page=2, page=1)[1]
@@ -141,11 +152,11 @@ def just_build_package(gitpkg):
                          'is "{1}" instead of the expected "sucess"'.format(last_pipeline.id, last_pipeline.status))
 
     print("Retrying pipeline {0}".format(last_pipeline.id))
-    print(last_pipeline)
-    last_pipeline.retry()
+    if not dry_run:
+        last_pipeline.retry()
 
 
-def wait_for_pipeline_to_finish(gitpkg, tag):
+def wait_for_pipeline_to_finish(gitpkg, tag, dry_run=False):
     sleep_step = 30
     max_sleep = 60 * 60  # one hour
     if tag == 'none':
@@ -155,8 +166,14 @@ def wait_for_pipeline_to_finish(gitpkg, tag):
         # otherwise just take the last pipeline
         pipeline = gitpkg.pipelines.list(per_page=1, page=1)[0]
 
-    # probe and wait for the pipeline to finish
     pipeline_id = pipeline.id
+
+    print('Waiting for the pipeline {0} of package {1} to finish. Do not interrupt.'.format(pipeline_id, gitpkg.name))
+
+    if dry_run:
+        return
+
+    # probe and wait for the pipeline to finish
     slept_so_far = 0
     while pipeline.status == 'running' or pipeline.status == 'pending':
         time.sleep(sleep_step)
@@ -172,8 +189,10 @@ def wait_for_pipeline_to_finish(gitpkg, tag):
         raise ValueError('Pipeline {0} of project {1} exited with undesired status "{2}". '
                          'Release is not possible.'.format(pipeline_id, gitpkg.name, pipeline.status))
 
+    print('Pipeline {0} of package {1} succeeded. Continue processing.'.format(pipeline_id, gitpkg.name))
+
 
-def release_package(gitpkg, tag_name, tag_comments_list):
+def release_package(gitpkg, tag_name, tag_comments_list, dry_run=False):
     # if there is nothing to release, just rebuild the package
     if tag_name == 'none':
         print("Since the tag is 'none', we just re-build the last pipeline")
@@ -186,25 +205,26 @@ def release_package(gitpkg, tag_name, tag_comments_list):
     readme_content = _update_readme(readme_content, version=version_number)
     # commit and push changes
     commit_files(gitpkg, {'README.rst': readme_content, 'version.txt': version_number},
-                 'Increased stable version to %s' % version_number)
+                 'Increased stable version to %s' % version_number, dry_run)
 
     # 2. Tag package with new tag and push
-    print("creating tag {}".format(tag_name))
-    print('package {0}, tag {1}, updating comments with:'.format(gitpkg.name, tag_name))
+    print("Creating tag {}".format(tag_name))
+    print("updating tag's comments with:".format(gitpkg.name, tag_name))
     print(tag_comments_list)
-    tag = gitpkg.tags.create({'tag_name': tag_name, 'ref': 'master'})
-    # update tag with comments
-    tag.set_release_description('\n'.join(tag_comments_list))
+    if not dry_run:
+        tag = gitpkg.tags.create({'tag_name': tag_name, 'ref': 'master'})
+        # update tag with comments
+        tag.set_release_description('\n'.join(tag_comments_list))
 
     # 3. Replace branch tag in Readme to master, change version file to beta version tag. Git add, commit, and push.
     readme_content = _update_readme(readme_content)
     version_number += 'b0'
     # commit and push changes
     commit_files(gitpkg, {'README.rst': readme_content, 'version.txt': version_number},
-                 'Increased latest version to %s [skip ci]' % version_number)
+                 'Increased latest version to %s [skip ci]' % version_number, dry_run)
 
 
-def parse_and_process_package_changelog(gl, bob_group, pkg_name, package_changelog):
+def parse_and_process_package_changelog(gl, bob_group, pkg_name, package_changelog, dry_run=False):
     cur_tag = None
     cur_tag_comments = []
 
@@ -218,7 +238,7 @@ def parse_and_process_package_changelog(gl, bob_group, pkg_name, package_changel
         if '  *' == line[:3]:  # a tag level
             # write the comments collected for the previous tag
             if cur_tag:
-                update_tag_comments(gitpkg, cur_tag, cur_tag_comments)
+                update_tag_comments(gitpkg, cur_tag, cur_tag_comments, dry_run)
                 cur_tag_comments = []  # reset comments
 
             # parse the current tag name
@@ -231,7 +251,7 @@ def parse_and_process_package_changelog(gl, bob_group, pkg_name, package_changel
     return gitpkg, cur_tag, cur_tag_comments
 
 
-def main(private_token, group_name='bob', changelog_file='changelog.rst'):
+def main(private_token, group_name='bob', changelog_file='changelog.rst', dry_run=False, package=None, resume=False):
     gl = gitlab.Gitlab('https://gitlab.idiap.ch', private_token=private_token, api_version=4)
     bob_group = gl.groups.list(search=group_name)[0]
 
@@ -244,19 +264,35 @@ def main(private_token, group_name='bob', changelog_file='changelog.rst'):
     # find the starts of each package's description in the changelog
     pkgs = numpy.asarray([i for i, line in enumerate(changelog) if line[0] == '*'])
     pkgs = numpy.concatenate([pkgs, [len(changelog)]])
-    for i in range(pkgs.shape[0] - 1):
-        print('Processing package {0}'.format(changelog[pkgs[i]]))
-        # print(changelog[pkgs[i] + 1: pkgs[i + 1]])
-        gitpkg, tag, tag_comments = parse_and_process_package_changelog(gl, bob_group, changelog[pkgs[i]][1:].strip(),
-                                                                        changelog[pkgs[i] + 1: pkgs[i + 1]])
+    start_idx = 0
+    if package:
+        # get the index where the package first appears in the list
+        start_idx = [i for i, line in enumerate(changelog) if line[1:].strip() == package]
+        if not start_idx:
+            print('Package {0} was not found in the changelog'.format(package))
+            return
+        start_idx = start_idx[0]
+
+    for i in range(start_idx, pkgs.shape[0] - 1):
+        cur_package_name = changelog[pkgs[i]][1:].strip()
+        print('\nProcessing package {0}'.format(changelog[pkgs[i]]))
+        gitpkg, tag, tag_comments = parse_and_process_package_changelog(gl, bob_group, cur_package_name,
+                                                                        changelog[pkgs[i] + 1: pkgs[i + 1]], dry_run)
         # release the package with the found tag and its comments
         if gitpkg:
-            release_package(gitpkg, tag, tag_comments)
+            release_package(gitpkg, tag, tag_comments, dry_run)
             # now, wait for the pipeline to finish, before we can release the next package
-            wait_for_pipeline_to_finish(gitpkg, tag)
+            wait_for_pipeline_to_finish(gitpkg, tag, dry_run)
+
+        # if package name is provided and resume is not set, process only this package
+        if package == cur_package_name and not resume:
+            break
+
+    print('\nFinished processing changelog')
 
 
 if __name__ == '__main__':
     arguments = docopt(__doc__.format(sys.argv[0]), version='Changelog 0.0.1')
     main(arguments['<private_token>'], group_name=arguments['--group-name'],
-         changelog_file=arguments['--changelog-file'])
+         changelog_file=arguments['--changelog-file'], dry_run=arguments['--dry-run'],
+         package=arguments['--package'], resume=arguments['--resume'])
-- 
GitLab