This is an automated email from the ASF dual-hosted git repository.

ocket8888 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git


The following commit(s) were added to refs/heads/master by this push:
     new cbf74a9  Workflow to check for and PR minor Go revision updates (#5506)
cbf74a9 is described below

commit cbf74a9f3b23e3ea2e25c0cb67a18038c2426ebc
Author: Zach Hoffman <[email protected]>
AuthorDate: Tue Feb 16 15:39:11 2021 -0700

    Workflow to check for and PR minor Go revision updates (#5506)
    
    * Workflow to open a PR if a minor Go version update is available
    
    * Check if the branch or PR exists before trying to create it
    
    * Tests
    
    * Rename workflow
    
    * Quote and explain cron expression
    
    * Add workflow_dispatch trigger
    
    * Do not error out if a "go version" label was not found
    
    * Spaces -> tabs
    
    * Rename GoPRMaker module to go_pr_maker
    
    * Reorder imports
    
    * Add docstrings
    
    * Remove HTML comments
    
    * Revise grammar
    
    * Update golang.org/x/ dependencies after updating the Go version
    
    * If running from the apache organization, only run for the master branch
    
    * Mention in the PR template that the PR also updates golang.org/x/
    dependencies
    
    * Remove `f`s from f-strings that can be regular strings
    
    * Remove unused import
---
 .github/actions/pr-to-update-go/README.rst         |  74 ++++
 .../pr-to-update-go/pr_to_update_go/__main__.py    |  32 ++
 .../pr-to-update-go/pr_to_update_go/constants.py   |  23 ++
 .../pr-to-update-go/pr_to_update_go/go_pr_maker.py | 390 +++++++++++++++++++++
 .../pr-to-update-go/pr_to_update_go/pr_template.md |  79 +++++
 .github/actions/pr-to-update-go/requirements.txt   |  14 +
 .github/actions/pr-to-update-go/setup.cfg          |  37 ++
 .github/actions/pr-to-update-go/setup.py           |  20 ++
 .../pr-to-update-go/tests/test_go_pr_maker.py      |  36 ++
 .../actions/pr-to-update-go/update_golang_org_x.sh |  56 +++
 .github/workflows/pr-to-update-go.yml              |  48 +++
 11 files changed, 809 insertions(+)

diff --git a/.github/actions/pr-to-update-go/README.rst 
b/.github/actions/pr-to-update-go/README.rst
new file mode 100644
index 0000000..c902404
--- /dev/null
+++ b/.github/actions/pr-to-update-go/README.rst
@@ -0,0 +1,74 @@
+..
+..
+.. Licensed under the Apache License, Version 2.0 (the "License");
+.. you may not use this file except in compliance with the License.
+.. You may obtain a copy of the License at
+..
+..     http://www.apache.org/licenses/LICENSE-2.0
+..
+.. Unless required by applicable law or agreed to in writing, software
+.. distributed under the License is distributed on an "AS IS" BASIS,
+.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+.. See the License for the specific language governing permissions and
+.. limitations under the License.
+..
+
+***************
+pr-to-update-go
+***************
+
+Opens a PR if a new minor Go revision is available.
+
+For example, if the ``GO_VERSION`` contains ``1.14.7`` but Go versions 1.15.1 
and 1.14.8 are available, it will
+
+1. Create a branch named ``go-1.14.8`` to update the repo's Go version to 
1.14.8
+2. Updates all golang.org/x/ dependencies of the project, since these are 
meant to be updated with the Go compiler.
+3. Open a PR targeting the ``master`` branch from branch ``go-1.14.8``
+
+Other behavior in this scenario:
+
+- If a branch named ``go-1.14.8`` already exists, no additional branch is 
created.
+- If a PR titled *Update Go version to 1.14.8* already exists, no additional 
PR is opened.
+
+Environment Variables
+=====================
+
++----------------------------+----------------------------------------------------------------------------------+
+| Environment Variable Name  | Value                                           
                                 |
++============================+==================================================================================+
+| ``GIT_AUTHOR_NAME``        | Optional. The username to associate with the 
commit that updates the Go version. |
++----------------------------+----------------------------------------------------------------------------------+
+| ``GITHUB_TOKEN``           | Required. ``${{ github.token }}`` or ``${{ 
secrets.GITHUB_TOKEN }}``             |
++----------------------------+----------------------------------------------------------------------------------+
+| ``GO_VERSION_FILE``        | Required. The file in the repo containing the 
version of Go used by the repo.    |
++----------------------------+----------------------------------------------------------------------------------+
+
+
+Outputs
+=======
+
+``exit-code``
+-------------
+
+Exit code is 0 unless an error was encountered.
+
+Example usage
+=============
+
+.. code-block:: yaml
+
+       - name: PR to Update Go
+         run: python3 -m pr_to_update_go
+         env:
+           GIT_AUTHOR_NAME: asfgit
+           GITHUB_TOKEN: ${{ github.token }}
+           GO_VERSION_FILE: GO_VERSION
+
+Tests
+=====
+
+To run the unit tests:
+
+.. code-block:: shell
+
+       python3 -m unittest discover ./tests
diff --git a/.github/actions/pr-to-update-go/pr_to_update_go/__main__.py 
b/.github/actions/pr-to-update-go/pr_to_update_go/__main__.py
new file mode 100644
index 0000000..99b1c09
--- /dev/null
+++ b/.github/actions/pr-to-update-go/pr_to_update_go/__main__.py
@@ -0,0 +1,32 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+import os
+import sys
+
+from github.MainClass import Github
+
+from pr_to_update_go.go_pr_maker import GoPRMaker
+from pr_to_update_go.constants import ENV_GITHUB_TOKEN
+
+
+def main() -> None:
+       try:
+               github_token: str = os.environ[ENV_GITHUB_TOKEN]
+       except KeyError:
+               print(f'Environment variable {ENV_GITHUB_TOKEN} must be 
defined.')
+               sys.exit(1)
+       gh = Github(login_or_token=github_token)
+       GoPRMaker(gh).run()
+
+
+main()
diff --git a/.github/actions/pr-to-update-go/pr_to_update_go/constants.py 
b/.github/actions/pr-to-update-go/pr_to_update_go/constants.py
new file mode 100644
index 0000000..b3d8770
--- /dev/null
+++ b/.github/actions/pr-to-update-go/pr_to_update_go/constants.py
@@ -0,0 +1,23 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+from typing import Final
+
+ENV_GIT_AUTHOR_NAME: Final = 'GIT_AUTHOR_NAME'
+ENV_GITHUB_REPOSITORY: Final = 'GITHUB_REPOSITORY'
+ENV_GITHUB_REPOSITORY_OWNER: Final = 'GITHUB_REPOSITORY_OWNER'
+ENV_GITHUB_TOKEN: Final = 'GITHUB_TOKEN'
+ENV_GO_VERSION_FILE: Final = 'GO_VERSION_FILE'
+GIT_AUTHOR_EMAIL_TEMPLATE: Final = '{git_author_name}@users.noreply.github.com'
+GO_REPO_NAME: Final = 'golang/go'
+GO_VERSION_URL: Final = 'https://golang.org/dl/?mode=json'
+RELEASE_PAGE_URL: Final = 'https://golang.org/doc/devel/release.html'
diff --git a/.github/actions/pr-to-update-go/pr_to_update_go/go_pr_maker.py 
b/.github/actions/pr-to-update-go/pr_to_update_go/go_pr_maker.py
new file mode 100644
index 0000000..ada3672
--- /dev/null
+++ b/.github/actions/pr-to-update-go/pr_to_update_go/go_pr_maker.py
@@ -0,0 +1,390 @@
+#!/usr/bin/env python3
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+"""
+Generate pull requests that update a repository's Go version.
+
+Classes:
+
+    GoPRMaker
+
+"""
+import json
+import os
+import re
+import subprocess
+import sys
+from typing import Union
+
+import requests
+from github.Branch import Branch
+from github.Commit import Commit
+from github.GitCommit import GitCommit
+from github.GitTree import GitTree
+from github.GithubObject import NotSet
+from github.InputGitTreeElement import InputGitTreeElement
+from github.Requester import Requester
+
+from requests import Response
+
+from github.GithubException import BadCredentialsException, GithubException, 
UnknownObjectException
+from github.InputGitAuthor import InputGitAuthor
+from github.Label import Label
+from github.MainClass import Github
+from github.Milestone import Milestone
+from github.PaginatedList import PaginatedList
+from github.PullRequest import PullRequest
+from github.Repository import Repository
+
+from pr_to_update_go.constants import ENV_GITHUB_TOKEN, GO_VERSION_URL, 
ENV_GITHUB_REPOSITORY, \
+       ENV_GITHUB_REPOSITORY_OWNER, GO_REPO_NAME, RELEASE_PAGE_URL, 
ENV_GO_VERSION_FILE, \
+       ENV_GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL_TEMPLATE
+
+
+class GoPRMaker:
+       """
+       A class to generate pull requests for the purpose of updating the Go 
version in a repository.
+       """
+       gh: Github
+       latest_go_version: str
+       repo: Repository
+       author: InputGitAuthor
+
+       def __init__(self, gh: Github) -> None:
+               """
+               :param gh: Github
+               :rtype: None
+               """
+               self.gh = gh
+               repo_name: str = self.get_repo_name()
+               self.repo = self.get_repo(repo_name)
+
+               try:
+                       git_author_name = self.getenv(ENV_GIT_AUTHOR_NAME)
+                       git_author_email = 
GIT_AUTHOR_EMAIL_TEMPLATE.format(git_author_name=git_author_name)
+                       self.author = InputGitAuthor(git_author_name, 
git_author_email)
+               except KeyError:
+                       self.author = NotSet
+                       print('Will commit using the default author')
+
+       def branch_exists(self, branch: str) -> bool:
+               """
+               :param branch:
+               :type branch:
+               :return:
+               :rtype: bool
+               """
+               try:
+                       repo_go_version = self.get_repo_go_version(branch)
+                       if self.latest_go_version == repo_go_version:
+                               print(f'Branch {branch} already exists')
+                               return True
+               except GithubException as e:
+                       message = e.data.get('message')
+                       if not re.match(r'No commit found for the ref', 
message):
+                               raise e
+               return False
+
+       def update_branch(self, branch_name: str, sha: str) -> None:
+               """
+               :param branch_name:
+               :type branch_name:
+               :param sha:
+               :type sha:
+               :return:
+               :rtype: None
+               """
+               requester: Requester = self.repo._requester
+               patch_parameters = {
+                       'sha': sha,
+               }
+               requester.requestJsonAndCheck(
+                       'PATCH', self.repo.url + 
f'/git/refs/heads/{branch_name}', input=patch_parameters
+               )
+               return
+
+       def run(self) -> None:
+               """
+               :return:
+               :rtype: None
+               """
+               repo_go_version = self.get_repo_go_version()
+               self.latest_go_version = 
self.get_latest_major_upgrade(repo_go_version)
+               commit_message: str = f'Update Go version to 
{self.latest_go_version}'
+
+               source_branch_name: str = f'go-{self.latest_go_version}'
+               target_branch: str = 'master'
+               if repo_go_version == self.latest_go_version:
+                       print(f'Go version is up-to-date on {target_branch}, 
nothing to do.')
+                       return
+
+               if not self.branch_exists(source_branch_name):
+                       commit: Commit = 
self.set_go_version(self.latest_go_version, commit_message,
+                               source_branch_name)
+                       update_golang_org_x_commit: Union[GitCommit, None] = 
self.update_golang_org_x(commit)
+                       if isinstance(update_golang_org_x_commit, GitCommit):
+                               sha: str = update_golang_org_x_commit.sha
+                               self.update_branch(source_branch_name, sha)
+
+               owner: str = self.get_repo_owner()
+               self.create_pr(self.latest_go_version, commit_message, owner, 
source_branch_name,
+                       target_branch)
+
+       @staticmethod
+       def getenv(env_name: str) -> str:
+               """
+               :param env_name: str
+               :return:
+               :rtype: str
+               """
+               return os.environ[env_name]
+
+       def get_repo(self, repo_name: str) -> Repository:
+               """
+               :param repo_name: str
+               :return:
+               :rtype: Repository
+               """
+               try:
+                       repo: Repository = self.gh.get_repo(repo_name)
+               except BadCredentialsException:
+                       print(f'Credentials from {ENV_GITHUB_TOKEN} were bad.')
+                       sys.exit(1)
+               return repo
+
+       @staticmethod
+       def get_major_version(from_go_version: str) -> str:
+               """
+               :param from_go_version: str
+               :return:
+               :rtype: str
+               """
+               return re.search(pattern=r'^\d+\.\d+', 
string=from_go_version).group(0)
+
+       def get_latest_major_upgrade(self, from_go_version: str) -> str:
+               """
+               :param from_go_version: str
+               :return:
+               :rtype: str
+               """
+               major_version = self.get_major_version(from_go_version)
+               go_version_response: Response = requests.get(GO_VERSION_URL)
+               go_version_response.raise_for_status()
+               go_version_content: list = 
json.loads(go_version_response.content)
+               index = 0
+               fetched_go_version: str = ''
+               while True:
+                       if not go_version_content[index]['stable']:
+                               continue
+                       go_version_name: str = 
go_version_content[index]['version']
+                       fetched_go_version = re.search(pattern=r'[\d.]+', 
string=go_version_name).group(0)
+                       if major_version == 
self.get_major_version(fetched_go_version):
+                               break
+                       index += 1
+               if major_version != self.get_major_version(fetched_go_version):
+                       raise Exception(f'No supported {major_version} Go 
versions exist.')
+               print(f'Latest version of Go {major_version} is 
{fetched_go_version}')
+               return fetched_go_version
+
+       def get_repo_name(self) -> str:
+               """
+               :return:
+               :rtype: str
+               """
+               repo_name: str = self.getenv(ENV_GITHUB_REPOSITORY)
+               return repo_name
+
+       def get_repo_owner(self) -> str:
+               """
+               :return:
+               :rtype: str
+               """
+               repo_name: str = self.getenv(ENV_GITHUB_REPOSITORY_OWNER)
+               return repo_name
+
+       def get_go_milestone(self, go_version: str) -> str:
+               """
+               :param go_version: str
+               :return:
+               """
+               go_repo: Repository = self.get_repo(GO_REPO_NAME)
+               milestones: PaginatedList[Milestone] = 
go_repo.get_milestones(state='all', sort='due_on',
+                       direction='desc')
+               milestone_title = f'Go{go_version}'
+               for milestone in milestones:  # type: Milestone
+                       if milestone.title == milestone_title:
+                               print(f'Found Go milestone {milestone.title}')
+                               return milestone.raw_data.get('html_url')
+               raise Exception(f'Could not find a milestone named 
{milestone_title}.')
+
+       @staticmethod
+       def get_release_notes_page() -> str:
+               """
+               :return:
+               :rtype: str
+               """
+               release_history_response: Response = 
requests.get(RELEASE_PAGE_URL)
+               release_history_response.raise_for_status()
+               return release_history_response.content.decode()
+
+       @staticmethod
+       def get_release_notes(go_version: str, release_notes_content: str) -> 
str:
+               """
+               :param go_version: str
+               :param release_notes_content: str
+               :return:
+               :rtype: str
+               """
+               go_version_pattern = go_version.replace('.', '\\.')
+               release_notes_pattern: str = 
f'<p>\\s*\\n\\s*go{go_version_pattern}.*?</p>'
+               release_notes_matches = re.search(release_notes_pattern, 
release_notes_content,
+                       re.MULTILINE | re.DOTALL)
+               if release_notes_matches is None:
+                       raise Exception(f'Could not find release notes on 
{RELEASE_PAGE_URL}')
+               release_notes = re.sub(r'[\s\t]+', ' ', 
release_notes_matches.group(0))
+               return release_notes
+
+       def get_pr_body(self, go_version: str, milestone_url: str) -> str:
+               """
+               :param go_version: str
+               :param milestone_url: str
+               :return:
+               :rtype: str
+               """
+               with open(os.path.dirname(__file__) + '/pr_template.md') as 
file:
+                       pr_template = file.read()
+               go_major_version = self.get_major_version(go_version)
+
+               release_notes = self.get_release_notes(go_version, 
self.get_release_notes_page())
+               pr_body: str = pr_template.format(GO_VERSION=go_version, 
GO_MAJOR_VERSION=go_major_version,
+                       RELEASE_NOTES=release_notes, 
MILESTONE_URL=milestone_url)
+               print('Templated PR body')
+               return pr_body
+
+       def get_repo_go_version(self, branch: str = 'master') -> str:
+               """
+               :param branch: str
+               :return:
+               :rtype: str
+               """
+               return self.repo.get_contents(self.getenv(ENV_GO_VERSION_FILE),
+                       
f'refs/heads/{branch}').decoded_content.rstrip().decode()
+
+       def set_go_version(self, go_version: str, commit_message: str,
+                       source_branch_name: str) -> Commit:
+               """
+               :param go_version: str
+               :param commit_message: str
+               :param source_branch_name: str
+               :return:
+               :rtype: str
+               """
+               master: Branch = self.repo.get_branch('master')
+               sha: str = master.commit.sha
+               ref: str = f'refs/heads/{source_branch_name}'
+               self.repo.create_git_ref(ref, sha)
+
+               print(f'Created branch {source_branch_name}')
+               go_version_file: str = self.getenv(ENV_GO_VERSION_FILE)
+               go_file_contents = self.repo.get_contents(go_version_file, ref)
+               kwargs = {'path': go_version_file,
+                       'message': commit_message,
+                       'content': (go_version + '\n'),
+                       'sha': go_file_contents.sha,
+                       'branch': source_branch_name,
+               }
+               try:
+                       git_author_name = self.getenv(ENV_GIT_AUTHOR_NAME)
+                       git_author_email = 
GIT_AUTHOR_EMAIL_TEMPLATE.format(git_author_name=git_author_name)
+                       author: InputGitAuthor = 
InputGitAuthor(name=git_author_name, email=git_author_email)
+                       kwargs['author'] = author
+                       kwargs['committer'] = author
+               except KeyError:
+                       print('Committing using the default author')
+
+               commit: Commit = self.repo.update_file(**kwargs).get('commit')
+               print(f'Updated {go_version_file} on {self.repo.name}')
+               return commit
+
+       def update_golang_org_x(self, previous_commit: Commit) -> 
Union[GitCommit, None]:
+               """
+               :param previous_commit:
+               :type previous_commit:
+               :return:
+               :rtype: Union[GitCommit, None]
+               """
+               subprocess.run(['git', 'fetch', 'origin'], check=True)
+               subprocess.run(['git', 'checkout', previous_commit.sha], 
check=True)
+               script_path: str = 
'.github/actions/pr-to-update-go/update_golang_org_x.sh'
+               subprocess.run([script_path], check=True)
+               files_to_check: list[str] = ['go.mod', 'go.sum', 
'vendor/modules.txt']
+               tree_elements: list[InputGitTreeElement] = []
+               for file in files_to_check:
+                       diff_process = subprocess.run(['git', 'diff', 
'--exit-code', '--', file])
+                       if diff_process.returncode == 0:
+                               continue
+                       with open(file) as stream:
+                               content: str = stream.read()
+                       tree_element: InputGitTreeElement = 
InputGitTreeElement(path=file, mode='100644',
+                               type='blob', content=content)
+                       tree_elements.append(tree_element)
+               if len(tree_elements) == 0:
+                       print('No golang.org/x/ dependencies need to be 
updated.')
+                       return
+               tree_hash = subprocess.check_output(
+                       ['git', 'log', '-1', '--pretty=%T', 
previous_commit.sha]).decode().strip()
+               base_tree: GitTree = self.repo.get_git_tree(sha=tree_hash)
+               tree: GitTree = self.repo.create_git_tree(tree_elements, 
base_tree)
+               commit_message: str = f'Update golang.org/x/ dependencies for 
go{self.latest_go_version}'
+               previous_git_commit: GitCommit = 
self.repo.get_git_commit(previous_commit.sha)
+               git_commit: GitCommit = 
self.repo.create_git_commit(message=commit_message, tree=tree,
+                       parents=[previous_git_commit],
+                       author=self.author, committer=self.author)
+               print('Updated golang.org/x/ dependencies')
+               return git_commit
+
+       def create_pr(self, latest_go_version: str, commit_message: str, owner: 
str,
+                       source_branch_name: str, target_branch: str) -> None:
+               """
+               :param latest_go_version: str
+               :param commit_message: str
+               :param owner: str
+               :param source_branch_name: str
+               :param target_branch: str
+               :return:
+               :rtype: None
+               """
+               prs: PaginatedList = self.gh.search_issues(
+                       f'repo:{self.repo.full_name} is:pr is:open 
head:{source_branch_name}')
+               for list_item in prs:
+                       pr: PullRequest = self.repo.get_pull(list_item.number)
+                       if pr.head.ref != source_branch_name:
+                               continue
+                       print(f'Pull request for branch {source_branch_name} 
already exists:\n{pr.html_url}')
+                       return
+
+               milestone_url: str = self.get_go_milestone(latest_go_version)
+               pr_body: str = self.get_pr_body(latest_go_version, 
milestone_url)
+               pr: PullRequest = self.repo.create_pull(
+                       title=commit_message,
+                       body=pr_body,
+                       head=f'{owner}:{source_branch_name}',
+                       base=target_branch,
+                       maintainer_can_modify=True,
+               )
+               try:
+                       go_version_label: Label = self.repo.get_label('go 
version')
+                       pr.add_to_labels(go_version_label)
+               except UnknownObjectException:
+                       print('Unable to find a label named "go version".')
+               print(f'Created pull request {pr.html_url}')
diff --git a/.github/actions/pr-to-update-go/pr_to_update_go/pr_template.md 
b/.github/actions/pr-to-update-go/pr_to_update_go/pr_template.md
new file mode 100644
index 0000000..a3ecdd0
--- /dev/null
+++ b/.github/actions/pr-to-update-go/pr_to_update_go/pr_template.md
@@ -0,0 +1,79 @@
+## What does this PR (Pull Request) do?
+- [x] This PR is not related to any Issue
+
+This PR makes the Go components of Traffic Control build using Go version 
{GO_VERSION} and updates the `golang.org/x/` dependencies..
+
+See the Go {GO_VERSION} [release 
notes](https://golang.org/doc/devel/release.html#go{GO_MAJOR_VERSION}):
+
+<!--
+The release notes are licensed with the Go license.
+
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+> {RELEASE_NOTES}
+
+## Which Traffic Control components are affected by this PR?
+- CDN in a Box - Enroller
+- Grove
+- Traffic Control Client (Go)
+- Traffic Monitor
+- Traffic Ops
+- Traffic Ops ORT
+- Traffic Stats
+- CI tests for Go components
+
+## What is the best way to verify this PR?
+Run unit tests and API tests. Since this is only a patch-level version update, 
[the only changes]({MILESTONE_URL}?closed=1) were bugfixes. Breaking changes 
would be unexpected.
+
+## The following criteria are ALL met by this PR
+- [x] Existing tests are sufficient, no additional tests necessary
+- [x] The documentation only mentions the major Go version, no documentation 
updates necessary.
+- [x] The changelog already mentions updating to Go {GO_MAJOR_VERSION}, no 
additional changelog message necessary.
+- [x] This PR includes any and all required license headers
+- [x] This PR does not include a database migration
+- [x] This PR **DOES NOT FIX A SERIOUS SECURITY VULNERABILITY** (see [the 
Apache Software Foundation's security 
guidelines](https://www.apache.org/security/) for details)
+
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
diff --git a/.github/actions/pr-to-update-go/requirements.txt 
b/.github/actions/pr-to-update-go/requirements.txt
new file mode 100644
index 0000000..05cf2ea
--- /dev/null
+++ b/.github/actions/pr-to-update-go/requirements.txt
@@ -0,0 +1,14 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+PyGithub
+requests
\ No newline at end of file
diff --git a/.github/actions/pr-to-update-go/setup.cfg 
b/.github/actions/pr-to-update-go/setup.cfg
new file mode 100644
index 0000000..167f562
--- /dev/null
+++ b/.github/actions/pr-to-update-go/setup.cfg
@@ -0,0 +1,37 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+[metadata]
+name = pr-to-update-go
+version = 0.0.0
+description = Opens a PR if a new minor Go revision is available.
+long_description = file: README.rst
+long_description_content_type = text/x-rst
+author = Apache Traffic Control
+author_email = [email protected]
+classifiers = OSI Approved :: Apache Software License
+
+[options]
+python_requires = >=3.9
+packages = pr_to_update_go
+install_requires =
+    PyGithub
+    requests
+
+[options.entry_points]
+console_scripts = pr-to-update-go = pr_to_update_go:main
+
+[options.extras_require]
+test = unittest
+
+[options.package_data]
+pr_to_update_go = pr_template.md
diff --git a/.github/actions/pr-to-update-go/setup.py 
b/.github/actions/pr-to-update-go/setup.py
new file mode 100755
index 0000000..0b9856c
--- /dev/null
+++ b/.github/actions/pr-to-update-go/setup.py
@@ -0,0 +1,20 @@
+#!/usr/bin/env python3
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""
+The setuptools-based install script for the pr-to-update-go GitHub Action
+"""
+from setuptools import setup
+
+setup()
diff --git a/.github/actions/pr-to-update-go/tests/test_go_pr_maker.py 
b/.github/actions/pr-to-update-go/tests/test_go_pr_maker.py
new file mode 100644
index 0000000..1e0c378
--- /dev/null
+++ b/.github/actions/pr-to-update-go/tests/test_go_pr_maker.py
@@ -0,0 +1,36 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+from unittest import TestCase
+
+from pr_to_update_go.go_pr_maker import GoPRMaker
+
+
+class TestGoPRMaker(TestCase):
+       def test_get_major_version(self) -> None:
+               version: str = '1.2.3'
+               expected_major_version: str = '1.2'
+               actual_major_version: str = GoPRMaker.get_major_version(version)
+               self.assertEqual(expected_major_version, actual_major_version)
+               return
+
+       def test_get_release_notes(self) -> None:
+               go_version: str = '4.15.6'
+               expected_release_notes: str = f'<p> go4.15.6 The expected 
release notes </p>'
+               release_notes_with_whitespace: str = f"""<p>  
+                go{go_version} The expected release notes
+            </p>"""
+               content: str = f"""go4.15.5 text before
+        {release_notes_with_whitespace}
+        text <p>after</p> 4.15.7"""
+               actual_release_notes: str = 
GoPRMaker.get_release_notes(go_version, content)
+               self.assertEqual(expected_release_notes, actual_release_notes)
diff --git a/.github/actions/pr-to-update-go/update_golang_org_x.sh 
b/.github/actions/pr-to-update-go/update_golang_org_x.sh
new file mode 100755
index 0000000..4410b82
--- /dev/null
+++ b/.github/actions/pr-to-update-go/update_golang_org_x.sh
@@ -0,0 +1,56 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+set -o errexit -o nounset
+trap 'echo "Error on line ${LINENO} of ${0}"; exit 1' ERR
+
+# download_go downloads and installs the GO version specified in GO_VERSION
+download_go() {
+       . build/functions.sh
+       if verify_and_set_go_version; then
+               return
+       fi
+       go_version="$(cat "${GITHUB_WORKSPACE}/GO_VERSION")"
+       wget -O go.tar.gz 
"https://dl.google.com/go/go${go_version}.linux-amd64.tar.gz"; --no-verbose
+       echo "Extracting Go ${go_version}..."
+       <<-'SUDO_COMMANDS' sudo sh
+               set -o errexit
+    go_dir="$(command -v go | xargs realpath | xargs dirname | xargs dirname)"
+               mv "$go_dir" "${go_dir}.unused"
+               tar -C /usr/local -xzf go.tar.gz
+       SUDO_COMMANDS
+       rm go.tar.gz
+       go version
+}
+
+GOROOT=/usr/local/go
+export PATH="${PATH}:${GOROOT}/bin"
+export GOPATH="${HOME}/go"
+
+download_go
+
+# update all golang.org/x dependencies in go.mod/go.sum
+go get -u \
+       golang.org/x/crypto \
+       golang.org/x/net \
+       golang.org/x/sys \
+       golang.org/x/text \
+       golang.org/x/xerrors
+
+# update vendor/modules.txt
+go mod vendor -v
diff --git a/.github/workflows/pr-to-update-go.yml 
b/.github/workflows/pr-to-update-go.yml
new file mode 100644
index 0000000..fb8575a
--- /dev/null
+++ b/.github/workflows/pr-to-update-go.yml
@@ -0,0 +1,48 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+name: Is it time to update Go?
+
+on:
+  # run manually
+  workflow_dispatch:
+  schedule:
+    # 14:00 UTC every day
+    - cron: '0 14 * * *'
+
+jobs:
+  pr-to-update-go:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout repo
+        uses: actions/checkout@master
+        if: ${{ (github.repository_owner == 'apache' && github.ref == 
'refs/heads/master' ) || github.event_name != 'schedule' }}
+        id: checkout
+      - name: Install Python 3.9
+        uses: actions/setup-python@v2
+        if: ${{ steps.checkout.outcome == 'success' }}
+        with: { python-version: 3.9 }
+      - name: Install dependencies
+        if: ${{ steps.checkout.outcome == 'success' }}
+        run: pip install .github/actions/pr-to-update-go
+      - name: PR to Update Go
+        if: ${{ steps.checkout.outcome == 'success' }}
+        run: python3 -m pr_to_update_go
+        env:
+          GIT_AUTHOR_NAME: asfgit
+          GITHUB_TOKEN: ${{ github.token }}
+          GO_VERSION_FILE: GO_VERSION

Reply via email to