From c6a3b147331288fc543c7136d77ee568d8dd45c5 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Mon, 25 Nov 2024 17:04:30 +1300 Subject: [PATCH] ci: adjust the release process to handle publishing ops and ops-scenario (#1432) Adds new publish workflows for ops-scenario (one for main PyPI, one for test.pypi.org), modelled on the ops workflows. These differ significantly from the old ops-scenario publish workflow (now deleted), which would automatically create a GitHub release and tag and publish to PyPI (with API credentials) on a push to main. To avoid confusion, the publish workflows for `ops` are renamed (and while this is being done, the extension is changed from `yml` to `yaml` to match all the other files). The publish workflows are trigged by new tags - `2.*` for `ops` (rather than a release being published) and `scenario-7.*` for `ops-scenario`. This allows releases that only include publishing one of the packages (for example, in a month that had no changes to one, or when a patch release is required). The HACKING.md documentation is updated to reflect the changes in the release process. A CHANGES.md is added for Scenario, backdated to 7.0.0 (although 7.0.0 is not entirely complete, but it does list the main changes). Once this is merged, trusted publishing will need to be configured for the `ops-scenario` package, and the config for `ops` will need to be adjusted to refer to the new workflow filename. Drive-bys: * The smoke test workflow had incorrect indentation for the `schedule` trigger. * Clarify when `(testing)` and `(harness)` scopes are used in conventional commits, now that we have Scenario in the repo. * Note the use of the various `.. juju` directives in documentation. * Minor clarifications in the release process based on comments from @james-garner-canonical when he did his first release. --- .github/workflows/build_scenario_wheels.yaml | 61 ----- ...{publish.yml => publish-ops-scenario.yaml} | 11 +- .github/workflows/publish-ops.yaml | 39 ++++ .github/workflows/smoke.yaml | 4 +- .../workflows/test-publish-ops-scenario.yaml | 35 +++ ...test-publish.yml => test-publish-ops.yaml} | 2 +- HACKING.md | 209 ++++++++++++++---- testing/CHANGES.md | 43 ++++ 8 files changed, 290 insertions(+), 114 deletions(-) delete mode 100644 .github/workflows/build_scenario_wheels.yaml rename .github/workflows/{publish.yml => publish-ops-scenario.yaml} (84%) create mode 100644 .github/workflows/publish-ops.yaml create mode 100644 .github/workflows/test-publish-ops-scenario.yaml rename .github/workflows/{test-publish.yml => test-publish-ops.yaml} (95%) create mode 100644 testing/CHANGES.md diff --git a/.github/workflows/build_scenario_wheels.yaml b/.github/workflows/build_scenario_wheels.yaml deleted file mode 100644 index 87833841a..000000000 --- a/.github/workflows/build_scenario_wheels.yaml +++ /dev/null @@ -1,61 +0,0 @@ -name: Build ops-scenario wheels - -# TODO: adjust this workflow to properly build ops-scenario from the operator repo -# and then this should be adjusted to run when appropriate (not on push to main -# any more, but as part of the releasing workflow we agree on). -on: - workflow_dispatch -# push: -# branches: -# - main - -jobs: - build_wheel: - name: Build wheel on ubuntu (where else???) - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-python@v5 - - - name: Install build - run: pip install build - - - name: Build wheel - run: python -m build - - - uses: actions/upload-artifact@v3 - with: - path: ./dist/*.whl - - - name: Get the version - id: get_version - run: echo "VERSION=$(sed -n 's/^ *version.*=.*"\([^"]*\)".*/\1/p' pyproject.toml)" >> $GITHUB_OUTPUT - - - name: release - uses: actions/create-release@v1 - id: create_release - with: - draft: false - prerelease: false - tag_name: ${{ steps.get_version.outputs.VERSION }} - release_name: ${{ steps.get_version.outputs.VERSION }} - - env: - GITHUB_TOKEN: ${{ github.token }} - - - name: upload wheel - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ github.token }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./dist/ops_scenario-${{ steps.get_version.outputs.VERSION }}-py3-none-any.whl - asset_name: ops_scenario-${{ steps.get_version.outputs.VERSION }}-py3-none-any.whl - asset_content_type: application/wheel - - - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish-ops-scenario.yaml similarity index 84% rename from .github/workflows/publish.yml rename to .github/workflows/publish-ops-scenario.yaml index 05c1527e8..15664910f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish-ops-scenario.yaml @@ -1,8 +1,8 @@ name: Publish on: - release: - types: - - published + push: + tags: + - 'scenario-7.*' jobs: framework-tests: @@ -12,7 +12,7 @@ jobs: hello-charm-tests: uses: ./.github/workflows/hello-charm-tests.yaml build-n-publish: - name: Build and Publish to PyPI + name: Build and Publish ops-scenario to PyPI runs-on: ubuntu-latest permissions: id-token: write @@ -27,9 +27,10 @@ jobs: run: pip install wheel build - name: Build run: python -m build + working-directory: ./testing - name: Attest build provenance uses: actions/attest-build-provenance@v1.4.3 with: - subject-path: 'dist/*' + subject-path: 'testing/dist/*' - name: Publish uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/publish-ops.yaml b/.github/workflows/publish-ops.yaml new file mode 100644 index 000000000..c391664a4 --- /dev/null +++ b/.github/workflows/publish-ops.yaml @@ -0,0 +1,39 @@ +name: Publish +on: + push: + tags: + # TODO: When we come to ops v3.x we will need to reconsider what to do + # here (we might want to start prefixing with 'v' for one thing), and we + # will likely be publishing at least three packages: ops, ops-scenario, + # and ops-harness. + - '2.*' + +jobs: + framework-tests: + uses: ./.github/workflows/framework-tests.yaml + observability-charm-tests: + uses: ./.github/workflows/observability-charm-tests.yaml + hello-charm-tests: + uses: ./.github/workflows/hello-charm-tests.yaml + build-n-publish: + name: Build and Publish ops to PyPI + runs-on: ubuntu-latest + permissions: + id-token: write + attestations: write + contents: read + needs: [framework-tests, observability-charm-tests, hello-charm-tests] + steps: + - uses: actions/checkout@v4 + - name: Setup Python + uses: actions/setup-python@v5 + - name: Install build dependencies + run: pip install wheel build + - name: Build + run: python -m build + - name: Attest build provenance + uses: actions/attest-build-provenance@v1.4.3 + with: + subject-path: 'dist/*' + - name: Publish + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/smoke.yaml b/.github/workflows/smoke.yaml index 3b413210a..f3392547a 100644 --- a/.github/workflows/smoke.yaml +++ b/.github/workflows/smoke.yaml @@ -2,8 +2,8 @@ name: ops Smoke Tests on: workflow_dispatch: - schedule: - - cron: '0 7 25 * *' + schedule: + - cron: '0 7 25 * *' jobs: test: diff --git a/.github/workflows/test-publish-ops-scenario.yaml b/.github/workflows/test-publish-ops-scenario.yaml new file mode 100644 index 000000000..398d5af1b --- /dev/null +++ b/.github/workflows/test-publish-ops-scenario.yaml @@ -0,0 +1,35 @@ +name: Test Publish +on: [workflow_dispatch, workflow_call] + +jobs: + framework-tests: + uses: ./.github/workflows/framework-tests.yaml + observability-charm-tests: + uses: ./.github/workflows/observability-charm-tests.yaml + hello-charm-tests: + uses: ./.github/workflows/hello-charm-tests.yaml + build-n-publish: + name: Build and Publish ops-scenario to Test PyPI + runs-on: ubuntu-latest + permissions: + id-token: write + attestations: write + contents: read + needs: [framework-tests, observability-charm-tests, hello-charm-tests] + steps: + - uses: actions/checkout@v4 + - name: Setup Python + uses: actions/setup-python@v5 + - name: Install build dependencies + run: pip install wheel build + - name: Build + run: python -m build + working-directory: ./testing + - name: Attest build provenance + uses: actions/attest-build-provenance@v1.4.3 + with: + subject-path: 'testing/dist/*' + - name: Publish to test.pypi.org + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ diff --git a/.github/workflows/test-publish.yml b/.github/workflows/test-publish-ops.yaml similarity index 95% rename from .github/workflows/test-publish.yml rename to .github/workflows/test-publish-ops.yaml index da6fb5567..dcfcb3b9d 100644 --- a/.github/workflows/test-publish.yml +++ b/.github/workflows/test-publish-ops.yaml @@ -9,7 +9,7 @@ jobs: hello-charm-tests: uses: ./.github/workflows/hello-charm-tests.yaml build-n-publish: - name: Build and Publish to Test PyPI + name: Build and Publish ops to Test PyPI runs-on: ubuntu-latest permissions: id-token: write diff --git a/HACKING.md b/HACKING.md index 0ba4ffb55..2873ede45 100644 --- a/HACKING.md +++ b/HACKING.md @@ -179,14 +179,17 @@ Pull requests should have a short title that follows the * revert * test -If the PR is limited to changes in ops.testing (Harness), also include the scope -`(harness)` in the title. At present, we do not add a scope in any other cases. +At present, we only add a scope in these cases: + +* If the PR is limited to changes in ops/_private/harness.py, also include the scope `(harness)` +* If the PR is limited to changes in testing/, also include the scope `(testing)` For example: * feat: add the ability to observe change-updated events * fix!: correct the type hinting for config data * docs(harness): clarify the types of exceptions that Harness.add_user_secret may raise +* ci(testing): adjust the workflow that publishes ops-scenario Note that the commit messages to the PR's branch do not need to follow the conventional commit format, as these will be squashed into a single commit to `main` @@ -202,10 +205,10 @@ The copyright information in existing files does not need to be updated when tho In general, new functionality should always be accompanied by user-focused documentation that is posted to -https://juju.is/docs/sdk. The content for this site is written and hosted on -https://discourse.charmhub.io/c/doc. New documentation should get a new +https://juju.is/docs/sdk. The content for this site is written and hosted on +https://discourse.charmhub.io/c/doc. New documentation should get a new topic/post on this Discourse forum and then should be linked into the main -docs navigation page(s) as appropriate. The ops library's SDK page +docs navigation page(s) as appropriate. The ops library's SDK page content is pulled from [the corresponding Discourse topic](https://discourse.charmhub.io/t/the-charmed-operator-software-development-kit-sdk-docs/4449). Each page on [juju.is](https://juju.is/docs/sdk) has a link at the bottom that @@ -221,13 +224,21 @@ Currently we don't publish separate versions of documentation for separate relea next to the relevant content (e.g. headings, etc.). The ops library's API reference is automatically built and published to -[ops.readthedocs.io](https://ops.readthedocs.io/en/latest/). Please be complete with +[ops.readthedocs.io](https://ops.readthedocs.io/en/latest/). Please be complete with docstrings and keep them informative for _users_. The published docs are always for the in-development (main branch) of ops, and do not include any notes -indicating changes or additions across versions - we encourage all charmers to +indicating changes or additions across ops versions - we encourage all charmers to promptly upgrade to the latest version of ops, and to refer to the release notes and changelog for learning about changes. +We do note when features behave differently when using different Juju versions. +Use the `.. jujuadded:: x.y` directive to indicate that the feature is only +available when using version x.y (or higher) of Juju, `..jujuchanged:: x.y` +when the feature's behaviour _in ops_ changes, and `..jujuremoved:: x.y` when +the feature will be available in ops but not in that version (or later) of Juju. +Unmarked features are assumed to work and be available in the current LTS +version of Juju. + During the release process, changes also get a new entry in [CHANGES.md](CHANGES.md). These are grouped into the same groupings as [commit messages](https://www.conventionalcommits.org/en/) @@ -307,47 +318,155 @@ the build frontend is [build](https://pypi.org/project/build/). # Publishing a Release -To make a release of the ops library, do the following: - -1. Visit the [releases page on GitHub](https://github.com/canonical/operator/releases). -2. Click "Draft a new release" -3. The "Release Title" is simply the full version number, in the form `..` - and a brief summary of the main changes in the release - E.g. 2.3.12 Bug fixes for the Juju foobar feature when using Python 3.12 -4. Use the "Generate Release Notes" button to get a copy of the changes into the - notes field. -5. Group the changes by the commit type (feat, fix, etc.) and use full names (e.g., "Features", - not "feat") for group headings. Strip the commit type prefix from the bullet point. Strip the - username (who did each commit) if the author is a member of the Charm Tech team. -6. Where appropriate, collapse multiple tightly related bullet points into a - single point that refers to multiple commits. -7. Create a new branch, and copy this text to the [CHANGES.md](CHANGES.md) file, - stripping out links, who did each commit, the new contributor list, and the - link to the full changelog. -8. Change [version.py](ops/version.py)'s `version` to the - [appropriate string](https://semver.org/). -9. Check if there's a `chore: update charm pins` auto-generated PR in the queue. If it looks - good, merge it and check that tests still pass. If needed, you can re-trigger the - `Update Charm Pins` workflow manually to ensure latest charms and ops get tested. -10. Add, commit, and push, and open a PR to get the changelog and version bump +To make a release of the `ops` and/or `ops-scenario` packages, do the following: + +1. Check if there's a `chore: update charm pins` auto-generated PR in the queue. + If it looks good, merge it and check that tests still pass. If needed, you + can re-trigger the `Update Charm Pins` workflow manually to ensure latest + charms and ops get tested. +2. Visit the [releases page on GitHub](https://github.com/canonical/operator/releases). +3. Click "Draft a new release" +4. The "Release Title" is the full version numbers of ops and/or ops-scenario, + in the form `ops .. and ops-scenario ..` + and a brief summary of the main changes in the release. + For example: `ops 2.3.12 Bug fixes for the Juju foobar feature when using Python 3.12` +5. If the last release was for both `ops` and `ops-scenario`, leave the previous + tag choice on `auto`. If the last release was for only one package, change + the previous tag to be the last time the same package(s) were being released. +6. Have the release create a new tag, in the form `..` for + `ops` and `scenario-..` for `ops-scenario`. If releasing + both packages, use the ops tag. +7. Use the "Generate Release Notes" button to get a copy of the changes into the + notes field. The 'Release Documentation' section below details the form that + the release notes and changelog should take. +8. For `ops`, change [version.py](ops/version.py)'s `version` to the + appropriate string. For `ops-scenario`, change the version in + [testing/pyproject.toml](testing/pyproject.toml). Both packages use + [semantic versioning]](https://semver.org/), and adjust independently + (that is: ops 2.18 doesn't imply ops-scenario 2.18, or any other number). +9. Add, commit, and push, and open a PR to get the changelogs and version bumps into main (and get it merged). -11. Back in the GitHub releases page, tweak the release notes - for example, - you might want to have a short paragraph at the intro on particularly - noteworthy changes. -12. Have someone else in the Charm-Tech team proofread the release notes. -13. When you are ready, click "Publish". (If you are not ready, click "Save as Draft".) +10. Save the release notes as a draft, and have someone else in the Charm-Tech + team proofread the release notes. +11. If the release includes both `ops` and `ops-scenario` packages, then push a + new tag in the form `scenario-..`. This is done by + executing `git tag scenario-x.y.z`, then `git push upstream tag scenario-x.y.z` locally + (assuming you have configured `canonical/operator` as a remote named + `upstream`). +12. When you are ready, click "Publish". GitHub will create the additional tag. + +Pushing the tags will trigger automatic builds for the Python packages and +publish them to PyPI ([ops](https://pypi.org/project/ops/) and +[ops-scenario](https://pypi.org/project/ops-scenario)) (authorisation is handled +via a [Trusted Publisher](https://docs.pypi.org/trusted-publishers/) relationship). +Note that it sometimes take a bit of time for the new releases to show up. + +See [.github/workflows/publish-ops.yaml](.github/workflows/publish-ops.yaml) and +[.github/workflows/publish-ops-scenario.yaml](.github/workflows/publish-ops-scenario.yaml) for details. +(Note that the versions in the YAML refer to versions of the GitHub actions, not the versions of the ops library.) -This will trigger an automatic build for the Python package and publish it to -[PyPI](https://pypi.org/project/ops/)) (authorisation is handled via a -[Trusted Publisher](https://docs.pypi.org/trusted-publishers/) relationship). -Note that it sometimes take a bit of time for the new release to show up. +You can troubleshoot errors on the [Actions Tab](https://github.com/canonical/operator/actions). -See [.github/workflows/publish.yml](.github/workflows/publish.yml) for details. (Note that the versions in publish.yml refer to versions of the GitHub actions, not the versions of the ops library.) +13. Announce the release on [Discourse](https://discourse.charmhub.io/c/framework/42) and [Matrix](https://matrix.to/#/#charmhub-charmdev:ubuntu.com). -You can troubleshoot errors on the [Actions Tab](https://github.com/canonical/operator/actions). +14. Open a PR to change the version strings to the expected + next version, with ".dev0" appended (for example, if 3.14.1 is the next + expected version, use `'3.14.1.dev0'`). + +## Release Documentation + +We produce several pieces of documentation for `ops` and `ops-scenario` +releases, each serving a separate purpose and covering a different level. + +Avoid using the word "Scenario", preferring "unit testing API" or "state +transition testing". Users should install `ops-scenario` with +`pip install ops[testing]` rather than using the `ops-scenario` package name +directly. + +### `git log` + +`git log` is used to see every change since a previous release. Obviously, no +special work needs to be done so that this is available. A link to the GitHub +view of the log will be included at the end of the GitHub release notes when +the "Generate Release Notes" button is used, in the form: + +``` +**Full Changelog**: https://github.com/canonical/operator/compare/2.17.0...2.18.0 +``` + +These changes include both `ops` and `ops-scenario`. If someone needs to see +changes only for one of the packages, then the `/testing/` folder can be +filtered in/out. + +### CHANGES.md + +A changelog is kept in version control that simply lists the changes in each +release, other than chores like bumping version numbers. The changelog for `ops` +is at the top level, in [CHANGES.md](CHANGES.md), and the changelog for +`ops-scenario` is in the `/testing` folder, [CHANGES.md](testing/CHANGES.md). +There will be overlap between the two files, as many PRs will include changes to +common infrastructure, or will adjust both `ops` and also the testing API in +`ops-scenario`. + +Adding the changes is done in preparation for a release. Use the "Generate +Release Notes" button in the GitHub releases page, and copy the text to the +CHANGES.md files. + +* Group the changes by the commit type (feat, fix, and so on) and use full names + ("Features", not "feat", "Fixes", not "fix") for group headings. +* Remove any bullets that do not apply to the package (`ops` only changes for + `ops-scenario`, and `ops-scenario` only changes for `ops`). +* Strip the commit type prefix from the bullet point, and capitalise the first + word. +* Strip the username (who did each commit) if the author is a member of the + Charm Tech team. +* Replace the link to the pull request with the PR number in parentheses. +* Where appropriate, collapse multiple tightly related bullet points into a + single point that refers to multiple commits. + +For example: the PR + +``` +* docs: clarify where StoredState is stored by @benhoyt in https://github.com/canonical/operator/pull/2006 +``` -13. Announce the release on [Discourse](https://discourse.charmhub.io/c/framework/42) and [Matrix](https://matrix.to/#/#charmhub-charmdev:ubuntu.com) +is added to the "Documentation" section as: + +``` +* Clarify where StoredState is stored (#2006) +``` + +### GitHub Release Notes + +The GitHub release notes include the list of changes found in the changelogs, +but: + +* If both `ops` and `ops-scenario` packages are being released, include all the + changes in the same set of release notes. If only one package is being + released, remove any bullets that apply only to the other package. +* The links to the PRs are left in full. +* Add a section above the list of changes that briefly outlines any key changes + in the release. + +### Discourse Release Announcement + +Post to the [framework category](https://discourse.charmhub.io/c/framework/42) +with a subject matching the GitHub release title. + +The post should resemble this: + +``` +The Charm Tech team has just released version x.y.z of ops! + +It’s available from PyPI by using `pip install ops`, and `pip install ops[testing]`, +which will pick up the latest version. Upgrade by running `pip install --upgrade ops`. + +The main improvements in this release are ... + +Read more in the [full release notes on GitHub](link to the GitHub release). +``` -14. Open a PR to change [version.py](ops/version.py)'s `version` to the expected - next version, with ".dev0" appended (for example, if 3.14.1 is the next expected version, use - `'3.14.1.dev0'`). +In the post, outline the key improvements both in `ops` and `ops-scenario` - +the point here is to encourage people to check out the full notes and to upgrade +promptly, so ensure that you entice them with the best that the new versions +have to offer. diff --git a/testing/CHANGES.md b/testing/CHANGES.md new file mode 100644 index 000000000..b60094e9a --- /dev/null +++ b/testing/CHANGES.md @@ -0,0 +1,43 @@ +# 7.0.5 - 20 Sep 2024 + +## Features + +* Use a slightly more strict type for `AnyJson` + +# 7.0.4 - 18 Sep 2024 + +## Chores + +* Add a `py.typed` file + +# 7.0.3 - 18 Sep 2024 + +## Fixes + +* `ops.Model.get_relation` should not raise when a relation with the specified ID does not exist + +# 7.0.2 - 13 Sep 2024 + +## Refactor + +* Adjustments to handle the upcoming release of ops 2.17 + +# 7.0.1 - 9 Sep 2024 + +## Fixes + +* Fix broken Python 3.8 compatibility. + +# 7.0.0 - 9 Sep 2024 + +## Features + +* Support for testing Pebble check events +* Container exec mocking can match against a command prefix +* Inspect a list of the commands that a charm has `exec`'d in a container +* Add consistency checks for `StoredState` +* Specifying your event is now done via `ctx.on` attributes +* The context manager is accessed via the `Context` object +* State collections are frozensets instead of lists +* Most classes now expect at least some arguments to be passed as keywords +* Secret tests are much simpler - particularly, revision numbers do not need to be managed