-
Notifications
You must be signed in to change notification settings - Fork 14
Release Best Practices Guide #149
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| # Releases | ||
|
|
||
| <pre align="center">Streamline software releases through consistent templates, automated aggregation and notifications.</pre> | ||
|
|
||
| ## Introduction | ||
|
|
||
| **Background**: Making releases can be a chore. From ensuring the release notes have helpful information to aggregating many updates and notifying your users - doing the process right can be time consuming and fraught with misunderstandings. In this guide we recommend a standardized, automated way to notify your users of new releases. For example, we recommend using a `.github/release.yml` template to allow for more readable release notes, showcasing the importance of each update. We also provide automation for aggregating releases from many repositories into a super-release that provides a holistic view of project progress. Finally, we recommend automated notifications through GitHub integrations to help keep everyone informed. | ||
riverma marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| **Use Cases**: | ||
riverma marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| - Automating release notes generation with a readable template | ||
| - Aggregating multiple repository releases for projects with many repositories | ||
| - Streamlining release notifications to keep teams and users informed automatically | ||
|
|
||
| --- | ||
|
|
||
| ## Prerequisites | ||
| * A GitHub account and repository | ||
riverma marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| * Basic understanding of GitHub Actions and workflows | ||
| * Access to IM channels like Slack for integration | ||
|
|
||
| --- | ||
|
|
||
| ## Quick Start | ||
| **[⬇️ Recommended GitHub release.yml Template](release.yml)** | ||
|
|
||
| _A customizable GitHub-specific release template. Customize it to fit your project's labeling scheme and presentation preferences for release notes and place it in your GitHub repository's `.github/release.yml` path._ | ||
|
|
||
| **[⬇️ Python Script to Aggregate GitHub Releases](gh_aggregate_release_notes.py)** | ||
|
|
||
| _A Python script that aggregates release notes from many repositories and generates a "super" release text._ | ||
|
|
||
| NOTE: You'll need a configuration file to use the above Python script. See step 2.2 below for an example file that you can customize for your project. | ||
|
|
||
|
|
||
| --- | ||
|
|
||
| ## Step-by-Step Guide | ||
|
|
||
| 1. **Customize Release Notes**: | ||
| - Create a `.github/release.yml` file in your repository. | ||
| - Download our [GitHub release notes template](release.yml) and place the contents into your `.github/release.yml` file. | ||
| - Customize our recommended template as a baseline and to configure how release notes are generated based on your project's issue labels. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @riverma Adding a picture(s) of how to this step generates release notes would be helpful for new users wanting to adopt SLIM.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great suggestion @pogi7 - will do here 👍 |
||
|
|
||
| 2. **Aggregate Releases**: | ||
| We recommend using a script to automatically aggregate release notes from multiple repositories. Details below. | ||
|
|
||
| 2.1 Download our script | ||
| - Copy/download our script to your local machine | ||
| ``` | ||
| curl --output gh_aggregate_release_notes.py https://raw.githubusercontent.com/NASA-AMMOS/slim/issue-97/docs/guides/documentation/releases/gh_aggregate_release_notes.py | ||
| ``` | ||
|
|
||
| 2.2 Create a configuration file with your project's release information. Save the file with extension `.yml` A sample is provided below: | ||
| ``` | ||
| github_token: <INSERT YOUR GH PERSONAL TOKEN> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could use a brief hint here on how to get a token—maybe down on the FAQ? In retrospect, I think this is awfully risky. Your token could afford a lot of permissions unless it's carefully scoped—and putting it into a file makes it too easy to check into version control (yay detect-secrets!) Maybe it should be an environment variable?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My comment is to link to the GH Actions documentation directly -- I wouldn't put "" here, but instead I would make it explicit with
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that's OK because "secrets.GITHUB_TOKEN" is mnemonically explicit.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @nutjob4life - I was debating between an environment variable and a file. I felt that a file could be kept secure on a system with permission control, whereas the environment variable approach keeps a credential on the command-line history. Though I see the risk of committing this file and your point! Are these our only two choices, I'm curious about best practices here. @jpl-jengelke - agreed to linking to GH directly and letting their docs handle the UI specifics. |
||
| urls: | ||
| - https://github.com/your-org/your-repo-1/releases/tag/v1.1.0 | ||
| - https://github.com/your-org/your-repo-2/releases/tag/v2.5.3 | ||
| ``` | ||
| 2.3 Run the script to generate aggregated release notes | ||
| ``` | ||
| $ python gh_aggregate_release_notes.py your_configuration_file.yml | ||
| ``` | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, I like the concept of building release notes based off the GH API and the script looks light enough. When I first read about this I though of using GH's built-in facilities to just copy it. Could it all be done in an action file without a separate Python component? What if the actual release notes stored in the 'releases' section of the repository are used or the macros that create them there are reused? I think if GH products are reused then GH handles maintenance, the notes are uniform and the maintenance footprint is reduced. (I can see Python versions and module releases being pesky as time goes forward, plus shouldn't there be a dependencies file (requirements.txt)? |
||
|
|
||
| 2.3 Review your aggregated release notes | ||
| - Your aggregated release notes should be printed to standard output (`stdout`) | ||
|
|
||
| 3. **Set Up Notifications**: | ||
| - Integrate GitHub with your IM channels, such as Slack, for example installing and configuring extensions like: [GitHub's Slack integration](https://slack.github.com). | ||
| - Conduct individual outreach to your customers/users to encourage them to watch your repository for release updates to stay informed about new releases through GitHub's notification system. | ||
|
|
||
| --- | ||
|
|
||
| ## Frequently Asked Questions (FAQ) | ||
|
|
||
| - Q: Can I customize which pull requests or issues appear in the release notes? | ||
| - A: Yes, by using labels and the `exclude-labels` field in the `.github/release.yml` file, you can control the inclusion of pull requests and issues in your release notes. | ||
|
|
||
| --- | ||
|
|
||
| ## Credits | ||
|
|
||
| **Authorship**: | ||
| - [@riverma](https://www.github.com/riverma) | ||
|
|
||
| **Acknowledgements**: | ||
| * [@hookhua](https://github.com/hookhua) for inspiration to make this guide. | ||
| * [HySDS project](https://github.com/hysds) for inspiration and feedback. | ||
|
|
||
| --- | ||
|
|
||
| ## Feedback and Contributions | ||
|
|
||
| We welcome feedback and contributions to help improve and grow this page. Please see our [contribution guidelines](https://nasa-ammos.github.io/slim/docs/contribute/contributing/). | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| import requests | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The script should probably include some comments at the top about dependencies. For example, it does
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Echoes my comment above... Also, Python 3+.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also may want to add a little try-except error catching to explain to a casual user what's wrong. What if the template itself is munged up? That can be hard to catch. Some minimal logging (even just print statements) may be helpful, too. |
||
| import yaml | ||
| import argparse | ||
| from urllib.parse import urlparse | ||
| from time import sleep | ||
| from tqdm import tqdm | ||
| import random | ||
|
|
||
| def read_config(file_path): | ||
| """ | ||
| Reads the YAML configuration file and returns its contents. | ||
|
|
||
| Args: | ||
| file_path (str): The path to the YAML configuration file. | ||
|
|
||
| Returns: | ||
| dict: The contents of the YAML file if successful, None otherwise. | ||
| """ | ||
| with open(file_path, 'r') as stream: | ||
| try: | ||
| return yaml.safe_load(stream) | ||
| except yaml.YAMLError as exc: | ||
| print(exc) | ||
| return None | ||
|
|
||
| def get_release_notes(url, github_token): | ||
| """ | ||
| Extracts the owner, repo, and tag from the given GitHub URL and retrieves the release notes using the GitHub API. | ||
| Implements retries with exponential backoff and jitter for handling API rate limits and network issues. | ||
|
|
||
| Args: | ||
| url (str): The URL of the GitHub release tag page. | ||
| github_token (str): The GitHub token used for authentication. | ||
|
|
||
| Returns: | ||
| str: The release notes text if successful, empty string otherwise. | ||
| """ | ||
| parsed_url = urlparse(url) | ||
| hostname = parsed_url.hostname | ||
| path_parts = parsed_url.path.split('/') | ||
| owner, repo, tag = path_parts[1], path_parts[2], path_parts[-1] | ||
|
|
||
| # Be agnostic to GitHub.com or GitHub Enterprise repo URLs | ||
| api_url_prefix = f"https://api.github.com/repos/{owner}/{repo}" if hostname == "github.com" else f"https://{hostname}/api/v3/repos/{owner}/{repo}" | ||
| api_url = f"{api_url_prefix}/releases/tags/{tag}" | ||
| headers = { | ||
| "Authorization": f"token {github_token}", | ||
| "Accept": "application/vnd.github.v3+json" | ||
| } | ||
|
|
||
| max_retries = 5 | ||
| retry_num = 0 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could use Tenacity here to automate retries over the function. It would also handle the manifest other potential exceptions that could occur. |
||
| backoff_factor = 2 | ||
| while retry_num < max_retries: | ||
| response = requests.get(api_url, headers=headers) | ||
| if response.status_code == 200: | ||
| return response.json().get("body", "") | ||
| else: | ||
| print(f"Error fetching release notes for {url}: {response.status_code}. Retrying...") | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe move the In other words, change from Not a huge deal but I've been known to be rather pedantic 😂
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let me look into this thanks @nutjob4life |
||
| sleep_time = backoff_factor * (2 ** retry_num) + random.uniform(0, 1) | ||
| sleep(sleep_time) | ||
| retry_num += 1 | ||
|
|
||
| print(f"Failed to fetch release notes after {max_retries} attempts.") | ||
| return "" | ||
|
|
||
| def main(config_file): | ||
| """ | ||
| Main function to aggregate GitHub release notes. | ||
| It reads a YAML configuration file for GitHub repository URLs and a GitHub token, | ||
| then retrieves and prints the release notes for each repository URL listed. | ||
|
|
||
| Args: | ||
| config_file (str): The path to the YAML configuration file containing the GitHub token and URLs. | ||
| """ | ||
| config = read_config(config_file) | ||
| if not config: | ||
| print("Failed to read configuration.") | ||
| return | ||
|
|
||
| github_token = config.get("github_token") | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See comment above about putting the token into the |
||
| urls = config.get("urls", []) | ||
|
|
||
| release_notes = "" | ||
|
|
||
| for url in tqdm(urls, desc="Downloading release notes"): | ||
| notes = get_release_notes(url, github_token) | ||
| if notes: | ||
| repo_name = url.split("/")[4] # Assumes a specific URL structure. | ||
| release_notes += f"# {repo_name}\n\n{notes}\n\n" | ||
|
|
||
| print(release_notes) | ||
|
|
||
| if __name__ == "__main__": | ||
| parser = argparse.ArgumentParser(description="Aggregates GitHub release notes from a configuration file.") | ||
| parser.add_argument('config_file', type=str, help="Path to the YAML configuration file") | ||
| args = parser.parse_args() | ||
|
|
||
| main(args.config_file) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| # place in .github/release.yml | ||
|
|
||
| changelog: | ||
| exclude: # exclude any PRs labels we don't want in the changelog | ||
| labels: | ||
| - ignore-for-release | ||
| - skip-changelog | ||
| categories: # group PRs by your most important labels. Add / customize as needed. | ||
| - title: "🚀 Features" | ||
| labels: | ||
| - enhancement | ||
| - title: "🐛 Bug Fixes" | ||
| labels: | ||
| - bug | ||
| - title: "📚 Documentation" | ||
| labels: | ||
| - documentation | ||
| - title: 📦 Dependencies | ||
| labels: | ||
| - dependencies | ||
| - title: 💻 Other Changes | ||
| labels: | ||
| - "*" | ||
| exclude: # place the list of all labels you've categorized above to avoid duplication! | ||
| labels: | ||
| - enhancement | ||
| - bug | ||
| - documentation | ||
| - dependencies |
Uh oh!
There was an error while loading. Please reload this page.