From e75ff19facdeed4a4085ac0ddfe33d2297052b87 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Thu, 11 Jul 2019 15:31:27 -0400 Subject: [PATCH] tools: update our release scripts (#7493) Functionally this removes the script which filed bugs to remove old config (per plan of record in #6271) adds a script which files bugs to remove old code (cleanup of runtime guarded features) updates our release process In practice, this just hot swaps what our deprecation script does and updates the docs. Risk Level: n/a (tooling + comment) Testing: manual testing. Will do one more manual test with actual bug filing just before submitting. Docs Changes: updated Release Notes: n/a Fixes #6472 Signed-off-by: Alyssa Wilk --- GOVERNANCE.md | 2 +- source/common/runtime/runtime_features.cc | 5 - tools/deprecate_version/deprecate_version.py | 114 ++++++++++++------- 3 files changed, 76 insertions(+), 45 deletions(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 6639dbffca17..aa5cb0da5972 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -101,7 +101,7 @@ or you can subscribe to the iCal feed [here](https://app.opsgenie.com/webcal/get the same time, also add a new empty "pending" section to the [release notes](docs/root/intro/version_history.rst) and to [deprecated log](docs/root/intro/deprecated.rst) for the following version. E.g., "1.7.0 (pending)". -* Run the deprecate_versions.py script (e.g. `sh tools/deprecate_version/deprecate_version.sh 1.8.0 1.10.0`) +* Run the deprecate_versions.py script (e.g. `sh tools/deprecate_version/deprecate_version.sh`) to file tracking issues for code which can be removed. * Run the deprecate_features.py script (e.g. `sh tools/deprecate_version/deprecate_features.sh`) to make the last release's deprecated features fatal-by-default. Submit the resultant PR and send diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index e9c20ff38199..bb1800a1b198 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -38,11 +38,6 @@ constexpr const char* runtime_features[] = { // // The release cycle after a feature has been marked disallowed, it is officially removable, and // the maintainer team will run a script creating a tracking issue for proto and code clean up. -// -// TODO(alyssawilk) handle deprecation of reloadable_features and update the above comment. Ideally -// runtime override of a deprecated feature will log(warn) on runtime-load if not deprecated -// and hard-fail once it has been deprecated. - constexpr const char* disallowed_features[] = { // Acts as both a test entry for deprecated.proto and a marker for the Envoy // deprecation scripts. diff --git a/tools/deprecate_version/deprecate_version.py b/tools/deprecate_version/deprecate_version.py index b73c152d6d56..caeecd0d25a3 100644 --- a/tools/deprecate_version/deprecate_version.py +++ b/tools/deprecate_version/deprecate_version.py @@ -1,15 +1,14 @@ -# Script for automating cleanup PR creation for deprecated features at a given -# version. This script is generally run via +# Script for automating cleanup PR creation for deprecated runtime features # # sh tools/deprecate_version/deprecate_version.sh # # Direct usage (not recommended): # -# python tools/deprecate_version/deprecate_version.py <2 releases ago> +# python tools/deprecate_version/deprecate_version.py # # e.g # -# python tools/deprecate_version/deprecate_version.py 1.5.0 1.7.0 +# python tools/deprecate_version/deprecate_version.py # # A GitHub access token must be set in GH_ACCESS_TOKEN. To create one, go to # Settings -> Developer settings -> Personal access tokens in GitHub and create @@ -20,14 +19,13 @@ # Known issues: # - Minor fixup PRs (e.g. fixing a typo) will result in the creation of spurious # issues. -# - Later PRs can clobber earlier changed to DEPRECATED.md, meaning we miss -# issues. from __future__ import print_function from collections import defaultdict import os import re +import subprocess import sys import github @@ -72,25 +70,15 @@ def GetConfirmation(): return input('Creates issues? [yN] ').strip().lower() in ('y', 'yes') -def CreateIssues(deprecate_for_version, deprecate_by_version, access_token, commits): - """Create issues in GitHub corresponding to a set of commits. +def CreateIssues(access_token, runtime_and_pr): + """Create issues in GitHub for code to clean up old runtime guarded features. Args: - deprecate_for_version: string providing version to deprecate for, e.g. - 1.6.0. - deprecate_by_version: string providing version to deprecate by, e.g. 1.7.0. access_token: GitHub access token (see comment at top of file). - commits: set of git commit objects. + runtime_and_pr: a list of runtime guards and the PRs they were added. """ repo = github.Github(access_token).get_repo('envoyproxy/envoy') - # Find GitHub milestone object for deprecation target. - milestone = None - for m in repo.get_milestones(): - if m.title == deprecate_by_version: - milestone = m - break - if not milestone: - raise DeprecateVersionError('Unknown milestone %s' % deprecate_by_version) + # Find GitHub label objects for LABELS. labels = [] for label in repo.get_labels(): @@ -98,19 +86,19 @@ def CreateIssues(deprecate_for_version, deprecate_by_version, access_token, comm labels.append(label) if len(labels) != len(LABELS): raise DeprecateVersionError('Unknown labels (expected %s, got %s)' % (LABELS, labels)) - # What are the PRs corresponding to the commits? - prs = (int(re.search('\(#(\d+)\)', c.message).group(1)) for c in commits) + issues = [] - for pr in sorted(prs): + for runtime_guard, pr in runtime_and_pr: # Who is the author? pr_info = repo.get_pull(pr) - title = '[v%s deprecation] Remove features marked deprecated in #%d' % (deprecate_for_version, - pr) - body = ('#%d (%s) introduced a deprecation notice for v%s. This issue ' - 'tracks source code cleanup.') % (pr, pr_info.title, deprecate_for_version) + + title = '%s deprecation' % (runtime_guard) + body = ('#%d (%s) introduced a runtime guarded feature. This issue ' + 'tracks source code cleanup.') % (pr, pr_info.title) print(title) print(body) print(' >> Assigning to %s' % pr_info.user.login) + # TODO(htuch): Figure out how to do this without legacy and faster. exists = repo.legacy_search_issues('open', '"%s"' % title) or repo.legacy_search_issues( 'closed', '"%s"' % title) @@ -118,12 +106,16 @@ def CreateIssues(deprecate_for_version, deprecate_by_version, access_token, comm print(' >> Issue already exists, not posting!') else: issues.append((title, body, pr_info.user)) + + if not issues: + print('No features to deprecate in this release') + return + if GetConfirmation(): print('Creating issues...') for title, body, user in issues: try: - repo.create_issue( - title, body=body, assignees=[user.login], milestone=milestone, labels=labels) + repo.create_issue(title, body=body, assignees=[user.login], labels=labels) except github.GithubException as e: print(('GithubException while creating issue. This is typically because' ' a user is not a member of envoyproxy org. Check that %s is in ' @@ -131,18 +123,62 @@ def CreateIssues(deprecate_for_version, deprecate_by_version, access_token, comm raise +def GetRuntimeAlreadyTrue(): + """Returns a list of runtime flags already defaulted to true + """ + runtime_already_true = [] + runtime_features = re.compile(r'.*"(envoy.reloadable_features..*)",.*') + with open('source/common/runtime/runtime_features.cc', 'r') as features: + for line in features.readlines(): + match = runtime_features.match(line) + if match and 'test_feature_true' not in match.group(1): + print("Found existing flag " + match.group(1)) + runtime_already_true.append(match.group(1)) + + return runtime_already_true + + +def GetRuntimeAndPr(): + """Returns a list of tuples of [runtime features to deprecate, PR the feature was added] + """ + repo = Repo(os.getcwd()) + + runtime_already_true = GetRuntimeAlreadyTrue() + + # grep source code looking for reloadable features which are true to find the + # PR they were added. + grep_output = subprocess.check_output('grep -r "envoy.reloadable_features\." source/', shell=True) + features_to_flip = [] + runtime_feature_regex = re.compile(r'.*(source.*cc).*"(envoy.reloadable_features\.[^"]+)".*') + for line in grep_output.splitlines(): + match = runtime_feature_regex.match(str(line)) + if match: + filename = (match.group(1)) + runtime_guard = match.group(2) + # If this runtime guard isn't true, ignore it for this release. + if not runtime_guard in runtime_already_true: + continue + # For true runtime guards, walk the blame of the file they were added to, + # to find the pr the feature was added. + for commit, lines in repo.blame('HEAD', filename): + for line in lines: + if runtime_guard in line: + pr = (int(re.search('\(#(\d+)\)', commit.message).group(1))) + # Add the runtime guard and PR to the list to file issues about. + features_to_flip.append((runtime_guard, pr)) + + else: + print('no match in ' + str(line) + ' please address manually!') + + return features_to_flip + + if __name__ == '__main__': - if len(sys.argv) != 3: - print('Usage: %s ' % sys.argv[0]) - sys.exit(1) + runtime_and_pr = GetRuntimeAndPr() + access_token = os.getenv('GH_ACCESS_TOKEN') if not access_token: print('Missing GH_ACCESS_TOKEN') sys.exit(1) - deprecate_for_version = sys.argv[1] - deprecate_by_version = sys.argv[2] - history = GetHistory() - if deprecate_for_version not in history: - print('Unknown version: %s (valid versions: %s)' % (deprecate_for_version, history.keys())) - CreateIssues(deprecate_for_version, deprecate_by_version, access_token, - history[deprecate_for_version]) \ No newline at end of file + + CreateIssues(access_token, runtime_and_pr)