diff --git a/.github/workflows/prep-release.yml b/.github/workflows/prep-release.yml index 91bdd25c..030239fe 100644 --- a/.github/workflows/prep-release.yml +++ b/.github/workflows/prep-release.yml @@ -24,6 +24,8 @@ on: type: boolean jobs: prep_release: + permissions: + contents: write runs-on: ubuntu-latest strategy: fail-fast: true @@ -40,7 +42,7 @@ jobs: id: prep-release uses: jupyter-server/jupyter_releaser/.github/actions/prep-release@v2 with: - token: ${{ secrets.ADMIN_GITHUB_TOKEN }} + token: ${{ secrets.GITHUB_TOKEN }} version_spec: ${{ github.event.inputs.version_spec }} post_version_spec: ${{ github.event.inputs.post_version_spec }} target: ${{ github.event.inputs.target }} diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index d7810db0..02e37b32 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -17,6 +17,8 @@ on: jobs: publish_release: + permissions: + contents: write runs-on: ubuntu-latest strategy: fail-fast: true @@ -33,7 +35,7 @@ jobs: id: populate-release uses: jupyter-server/jupyter_releaser/.github/actions/populate-release@v2 with: - token: ${{ secrets.ADMIN_GITHUB_TOKEN }} + token: ${{ secrets.GITHUB_TOKEN }} target: ${{ github.event.inputs.target }} branch: ${{ github.event.inputs.branch }} release_url: ${{ github.event.inputs.release_url }} @@ -48,7 +50,7 @@ jobs: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} uses: jupyter-server/jupyter-releaser/.github/actions/finalize-release@v2 with: - token: ${{ secrets.ADMIN_GITHUB_TOKEN }} + token: ${{ secrets.GITHUB_TOKEN }} target: ${{ github.event.inputs.target }} release_url: ${{ steps.populate-release.outputs.release_url }} diff --git a/README.md b/README.md index f182f321..e1a7d93e 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ To install the latest release locally, make sure you have ## Checklist for Adoption -See the [adoption docs](https://jupyter-releaser.readthedocs.io/en/latest/how_to_guides/convert_repo.html). +See the [adoption guides](https://jupyter-releaser.readthedocs.io/en/latest/how_to_guides/index.html). ## Actions @@ -29,6 +29,6 @@ GitHub actions scripts are available to draft a changelog, draft a release, publ See the [action details documentation](https://jupyter-releaser.readthedocs.io/en/latest/background/theory.html#action-details) for more information. -The actions can be run on a [fork](https://jupyter-releaser.readthedocs.io/en/latest/how_to_guides/convert_repo_from_releaser.html#) of `jupyter_releaser` and target multiple -repositories, or run as workflows on the [source repositories](https://jupyter-releaser.readthedocs.io/en/latest/how_to_guides/convert_repo_from_repo), using +The actions can be run on a [fork](https://jupyter-releaser.readthedocs.io/en/latest/how_to_guides/convert_repo_from_releaser.html) of `jupyter_releaser` and target multiple +repositories, or run as workflows on the [source repositories](https://jupyter-releaser.readthedocs.io/en/latest/how_to_guides/convert_repo_from_repo.html), using shared credentials. diff --git a/docs/source/how_to_guides/convert_repo_from_repo.md b/docs/source/how_to_guides/convert_repo_from_repo.md index 27821864..7e28db9f 100644 --- a/docs/source/how_to_guides/convert_repo_from_repo.md +++ b/docs/source/how_to_guides/convert_repo_from_repo.md @@ -14,15 +14,12 @@ See [checklist](#Checklist-for-Adoption) below for details: ## Checklist for Adoption -- [ ] Add a [GitHub Access token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) with access to target GitHub repo to run GitHub Actions, saved as - `ADMIN_GITHUB_TOKEN` in the [repository secrets](https://docs.github.com/en/actions/reference/encrypted-secrets#creating-encrypted-secrets-for-a-repository). - The token needs to have `public_repo` and `repo:status` permissions. - [ ] Add access token for the [PyPI registry](https://packaging.python.org/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/#saving-credentials-on-github) stored as `PYPI_TOKEN`. _Note_ For security reasons, it is recommended that you scope the access to a single repository. - [ ] If needed, add access token for [npm](https://docs.npmjs.com/creating-and-viewing-access-tokens), saved as `NPM_TOKEN`. -- [ ] Enable tag protection for all tags (`*`), to ensure that only users - with admin write permissions can publish witht he shared credentials. +- [ ] Ensure that only trusted users with 2FA have admin access to the + repository, since they will be able to trigger releases. - [ ] Switch to Markdown Changelog - We recommend [MyST](https://myst-parser.readthedocs.io/en/latest/?badge=latest), especially if some of your docs are in reStructuredText. - Can use `pandoc -s changelog.rst -o changelog.md` and some hand edits as needed. diff --git a/jupyter_releaser/lib.py b/jupyter_releaser/lib.py index d5ab30c2..6c2be475 100644 --- a/jupyter_releaser/lib.py +++ b/jupyter_releaser/lib.py @@ -410,19 +410,18 @@ def prep_git(ref, branch, repo, auth, username, url): """Set up git""" repo = repo or util.get_repo() - user_name = "" try: - user_name = util.run("git config --global user.email") + util.run("git config --global user.email") + has_git_config = True except Exception: - pass + has_git_config = False - if not user_name: - # Use email address for the GitHub Actions bot + if not has_git_config: + # Default to the GitHub Actions bot # https://github.community/t/github-actions-bot-email-address/17204/6 - util.run( - 'git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"' - ) - util.run('git config --global user.name "GitHub Action"') + git_user_name = username or "41898282+github-actions[bot]" + util.run(f'git config --global user.email "{git_user_name}@users.noreply.github.com"') + util.run(f'git config --global user.name "{git_user_name}"') # Set up the repository checkout_dir = os.environ.get("RH_CHECKOUT_DIR", util.CHECKOUT_NAME) diff --git a/jupyter_releaser/tests/test_cli.py b/jupyter_releaser/tests/test_cli.py index e3b1d6a7..5f10e3ee 100644 --- a/jupyter_releaser/tests/test_cli.py +++ b/jupyter_releaser/tests/test_cli.py @@ -94,19 +94,18 @@ def test_prep_git_full(py_package, tmp_path, mocker, runner): os.mkdir(util.CHECKOUT_NAME) runner(["prep-git"], env=env) + mock_run.assert_has_calls( [ + call("echo before-prep-git >> 'log.txt'"), call("git config --global user.email"), - call( - 'git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"' - ), - call('git config --global user.name "GitHub Action"'), call("git init .jupyter_releaser_checkout"), call("git remote add origin https://snuffy:abc123@github.com/baz/bar.git"), call(f"{GIT_FETCH_CMD} --tags --force"), call(f"{GIT_FETCH_CMD} +refs/pull/42:refs/pull/42"), call(f"{GIT_FETCH_CMD} refs/pull/42"), call("git checkout -B foo refs/pull/42"), + call("git symbolic-ref -q HEAD"), ] ) diff --git a/jupyter_releaser/util.py b/jupyter_releaser/util.py index 24e658cd..fea6d682 100644 --- a/jupyter_releaser/util.py +++ b/jupyter_releaser/util.py @@ -558,6 +558,13 @@ def prepare_environment(fetch_draft_release=True): auth = os.environ.get("GITHUB_ACCESS_TOKEN", "") gh = get_gh_object(dry_run=dry_run, owner=owner, repo=repo_name, token=auth) + # Ensure the user is an admin. + if not dry_run: + user = gh.users.get_authenticated()["login"] + collab_level = gh.repos.get_collaborator_permission_level(user) + if not collab_level["permission"] == "admin": + raise RuntimeError(f"User {user} does not have admin permission") + # Get the latest draft release if none is given. release_url = os.environ.get("RH_RELEASE_URL") log(f"Environment release url was {release_url}")