diff --git a/.github/workflows/autofix_pr.yml b/.github/workflows/autofix_pr.yml index f055c078cf4f814e342697e311ad5660f68f4624..717c5e2fa5e3c35f3ff33d176f73022e7a0c95d4 100644 --- a/.github/workflows/autofix_pr.yml +++ b/.github/workflows/autofix_pr.yml @@ -97,6 +97,8 @@ jobs: with: app-id: ${{ secrets.ZED_ZIPPY_APP_ID }} private-key: ${{ secrets.ZED_ZIPPY_APP_PRIVATE_KEY }} + permission-contents: write + permission-workflows: write - name: steps::checkout_repo uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd with: diff --git a/.github/workflows/cherry_pick.yml b/.github/workflows/cherry_pick.yml index 4a3bd0e643e027e7feaeac4760797e2a1fb16e11..fc91aadee7b565f57cebc59affd2e94d3087696c 100644 --- a/.github/workflows/cherry_pick.yml +++ b/.github/workflows/cherry_pick.yml @@ -35,6 +35,8 @@ jobs: with: app-id: ${{ secrets.ZED_ZIPPY_APP_ID }} private-key: ${{ secrets.ZED_ZIPPY_APP_PRIVATE_KEY }} + permission-contents: write + permission-workflows: write - name: cherry_pick::run_cherry_pick::cherry_pick run: ./script/cherry-pick "$BRANCH" "$COMMIT" "$CHANNEL" env: diff --git a/tooling/xtask/src/tasks/workflows/autofix_pr.rs b/tooling/xtask/src/tasks/workflows/autofix_pr.rs index 2779dc2b01fa873bc050be4d873b9a5d502606bd..6fa7743275f36eda1746e7afdd4caabc429fec3c 100644 --- a/tooling/xtask/src/tasks/workflows/autofix_pr.rs +++ b/tooling/xtask/src/tasks/workflows/autofix_pr.rs @@ -2,7 +2,7 @@ use gh_workflow::*; use crate::tasks::workflows::{ runners, - steps::{self, FluentBuilder, NamedJob, named}, + steps::{self, FluentBuilder, NamedJob, RepositoryTarget, TokenPermissions, named}, vars::{self, StepOutput, WorkflowInput}, }; @@ -161,7 +161,13 @@ fn commit_changes(pr_number: &WorkflowInput, autofix_job: &NamedJob) -> NamedJob .add_env(("GITHUB_TOKEN", token)) } - let (authenticate, token) = steps::authenticate_as_zippy(); + let (authenticate, token) = steps::authenticate_as_zippy() + .for_repository(RepositoryTarget::current()) + .with_permissions([ + (TokenPermissions::Contents, Level::Write), + (TokenPermissions::Workflows, Level::Write), + ]) + .into(); named::job( Job::default() diff --git a/tooling/xtask/src/tasks/workflows/bump_patch_version.rs b/tooling/xtask/src/tasks/workflows/bump_patch_version.rs index 5ef149be29313bc2078dbc1f75a82845c3d3b666..7db348c1d5980c1b21780d9fe0af4e326f6283ca 100644 --- a/tooling/xtask/src/tasks/workflows/bump_patch_version.rs +++ b/tooling/xtask/src/tasks/workflows/bump_patch_version.rs @@ -63,7 +63,7 @@ fn run_bump_patch_version(branch: &WorkflowInput) -> steps::NamedJob { .add_env(("GITHUB_TOKEN", token)) } - let (authenticate, token) = steps::authenticate_as_zippy(); + let (authenticate, token) = steps::authenticate_as_zippy().into(); named::job( Job::default() diff --git a/tooling/xtask/src/tasks/workflows/cherry_pick.rs b/tooling/xtask/src/tasks/workflows/cherry_pick.rs index 5680bf6b23b85c17e68e531cecadfb31f091520d..ad8bc1c2c84acc9780bd5d803d893c4657335e90 100644 --- a/tooling/xtask/src/tasks/workflows/cherry_pick.rs +++ b/tooling/xtask/src/tasks/workflows/cherry_pick.rs @@ -2,7 +2,7 @@ use gh_workflow::*; use crate::tasks::workflows::{ runners, - steps::{self, NamedJob, named}, + steps::{self, NamedJob, RepositoryTarget, TokenPermissions, named}, vars::{StepOutput, WorkflowInput}, }; @@ -44,7 +44,13 @@ fn run_cherry_pick( .add_env(("GITHUB_TOKEN", token)) } - let (authenticate, token) = steps::authenticate_as_zippy(); + let (authenticate, token) = steps::authenticate_as_zippy() + .for_repository(RepositoryTarget::current()) + .with_permissions([ + (TokenPermissions::Contents, Level::Write), + (TokenPermissions::Workflows, Level::Write), + ]) + .into(); named::job( Job::default() diff --git a/tooling/xtask/src/tasks/workflows/extension_bump.rs b/tooling/xtask/src/tasks/workflows/extension_bump.rs index a1c2abc169f4348fd04a529c5a5b10b412464c9b..77d2acf7c830302407207950b1919b9002049460 100644 --- a/tooling/xtask/src/tasks/workflows/extension_bump.rs +++ b/tooling/xtask/src/tasks/workflows/extension_bump.rs @@ -359,7 +359,8 @@ fn trigger_release( let extension_registry = RepositoryTarget::new("zed-industries", &["extensions"]); let (generate_token, generated_token) = generate_token(&app_id.to_string(), &app_secret.to_string()) - .for_repository(extension_registry); + .for_repository(extension_registry) + .into(); let (get_extension_id, extension_id) = get_extension_id(); let (release_action, pull_request_number) = release_action(extension_id, tag, &generated_token); diff --git a/tooling/xtask/src/tasks/workflows/extension_workflow_rollout.rs b/tooling/xtask/src/tasks/workflows/extension_workflow_rollout.rs index 3a5d14603f97b43aacb581aaf3b970bac31b701f..1145cf2b5a70c30ac7212f6002e653d1396d55c4 100644 --- a/tooling/xtask/src/tasks/workflows/extension_workflow_rollout.rs +++ b/tooling/xtask/src/tasks/workflows/extension_workflow_rollout.rs @@ -6,6 +6,7 @@ use indoc::indoc; use serde_json::json; use crate::tasks::workflows::steps::CheckoutStep; +use crate::tasks::workflows::steps::TokenPermissions; use crate::tasks::workflows::steps::cache_rust_dependencies_namespace; use crate::tasks::workflows::vars::JobOutput; use crate::tasks::workflows::{ @@ -309,13 +310,17 @@ fn rollout_workflows_to_extension( } let (authenticate, token) = - generate_token(vars::ZED_ZIPPY_APP_ID, vars::ZED_ZIPPY_APP_PRIVATE_KEY).for_repository( - RepositoryTarget::new("zed-extensions", &["${{ matrix.repo }}"]).permissions([ - ("permission-pull-requests".to_owned(), Level::Write), - ("permission-contents".to_owned(), Level::Write), - ("permission-workflows".to_owned(), Level::Write), - ]), - ); + generate_token(vars::ZED_ZIPPY_APP_ID, vars::ZED_ZIPPY_APP_PRIVATE_KEY) + .for_repository(RepositoryTarget::new( + "zed-extensions", + &["${{ matrix.repo }}"], + )) + .with_permissions([ + (TokenPermissions::PullRequests, Level::Write), + (TokenPermissions::Contents, Level::Write), + (TokenPermissions::Workflows, Level::Write), + ]) + .into(); let (calculate_short_sha, short_sha) = get_short_sha(); @@ -372,10 +377,10 @@ fn create_rollout_tag(rollout_job: &NamedJob, filter_repos_input: &WorkflowInput } let (authenticate, token) = - generate_token(vars::ZED_ZIPPY_APP_ID, vars::ZED_ZIPPY_APP_PRIVATE_KEY).for_repository( - RepositoryTarget::current() - .permissions([("permission-contents".to_owned(), Level::Write)]), - ); + generate_token(vars::ZED_ZIPPY_APP_ID, vars::ZED_ZIPPY_APP_PRIVATE_KEY) + .for_repository(RepositoryTarget::current()) + .with_permissions([(TokenPermissions::Contents, Level::Write)]) + .into(); let job = Job::default() .needs([rollout_job.name.clone()]) diff --git a/tooling/xtask/src/tasks/workflows/publish_extension_cli.rs b/tooling/xtask/src/tasks/workflows/publish_extension_cli.rs index dad4bce45399bd8d0b4a6ff842f87830bd77484f..9f8d054241507af8597e2ff328263c440377686f 100644 --- a/tooling/xtask/src/tasks/workflows/publish_extension_cli.rs +++ b/tooling/xtask/src/tasks/workflows/publish_extension_cli.rs @@ -119,7 +119,8 @@ fn update_sha_in_extensions(publish_job: &NamedJob) -> NamedJob { let extensions_repo = RepositoryTarget::new("zed-industries", &["extensions"]); let (generate_token, generated_token) = generate_token(vars::ZED_ZIPPY_APP_ID, vars::ZED_ZIPPY_APP_PRIVATE_KEY) - .for_repository(extensions_repo); + .for_repository(extensions_repo) + .into(); fn checkout_extensions_repo(token: &StepOutput) -> Step { named::uses( diff --git a/tooling/xtask/src/tasks/workflows/release.rs b/tooling/xtask/src/tasks/workflows/release.rs index 2646005021e052681c0fa16a258a1d0dad725390..4d7dc24d5e2d78cae87339877d730d3e3fb945b0 100644 --- a/tooling/xtask/src/tasks/workflows/release.rs +++ b/tooling/xtask/src/tasks/workflows/release.rs @@ -179,7 +179,7 @@ fn validate_release_assets(deps: &[&NamedJob]) -> NamedJob { } fn auto_release_preview(deps: &[&NamedJob]) -> NamedJob { - let (authenticate, token) = steps::authenticate_as_zippy(); + let (authenticate, token) = steps::authenticate_as_zippy().into(); named::job( dependant_job(deps) diff --git a/tooling/xtask/src/tasks/workflows/steps.rs b/tooling/xtask/src/tasks/workflows/steps.rs index ebdd9b30538eb389a267a1c2fdb1822eec1d3a54..02476531ae97b6878784d2ce4694b29801446993 100644 --- a/tooling/xtask/src/tasks/workflows/steps.rs +++ b/tooling/xtask/src/tasks/workflows/steps.rs @@ -513,20 +513,50 @@ pub fn git_checkout(ref_name: &dyn std::fmt::Display) -> Step { .add_env(("REF_NAME", ref_name.to_string())) } +/// Non-exhaustive list of the permissions to be set for a GitHub app token. +/// +/// See https://github.com/actions/create-github-app-token?tab=readme-ov-file#permission-permission-name +/// and beyond for a full list of available permissions. +#[allow(unused)] +pub(crate) enum TokenPermissions { + Contents, + Issues, + PullRequests, + Workflows, +} + +impl TokenPermissions { + pub fn environment_name(&self) -> &'static str { + match self { + TokenPermissions::Contents => "permission-contents", + TokenPermissions::Issues => "permission-issues", + TokenPermissions::PullRequests => "permission-pull-requests", + TokenPermissions::Workflows => "permission-workflows", + } + } +} + pub(crate) struct GenerateAppToken<'a> { job_name: String, app_id: &'a str, app_secret: &'a str, repository_target: Option, + permissions: Option>, } impl<'a> GenerateAppToken<'a> { - pub fn for_repository(self, repository_target: RepositoryTarget) -> (Step, StepOutput) { + pub fn for_repository(self, repository_target: RepositoryTarget) -> Self { Self { repository_target: Some(repository_target), ..self } - .into() + } + + pub fn with_permissions(self, permissions: impl Into>) -> Self { + Self { + permissions: Some(permissions.into()), + ..self + } } } @@ -549,26 +579,24 @@ impl<'a> From> for (Step, StepOutput) { RepositoryTarget { owner, repositories, - permissions, }| { input .when_some(owner, |input, owner| input.add("owner", owner)) .when_some(repositories, |input, repositories| { input.add("repositories", repositories) }) - .when_some(permissions, |input, permissions| { - permissions.into_iter().fold( - input, - |input, (permission, level)| { - input.add( - permission, - serde_json::to_value(&level).unwrap_or_default(), - ) - }, - ) - }) }, - ), + ) + .when_some(token.permissions, |input, permissions| { + permissions + .into_iter() + .fold(input, |input, (permission, level)| { + input.add( + permission.environment_name(), + serde_json::to_value(&level).unwrap_or_default(), + ) + }) + }), ); let generated_token = StepOutput::new(&step, "token"); @@ -579,7 +607,6 @@ impl<'a> From> for (Step, StepOutput) { pub(crate) struct RepositoryTarget { owner: Option, repositories: Option, - permissions: Option>, } impl RepositoryTarget { @@ -587,7 +614,6 @@ impl RepositoryTarget { Self { owner: Some(owner.to_string()), repositories: Some(repositories.join("\n")), - permissions: None, } } @@ -595,14 +621,6 @@ impl RepositoryTarget { Self { owner: None, repositories: None, - permissions: None, - } - } - - pub fn permissions(self, permissions: impl Into>) -> Self { - Self { - permissions: Some(permissions.into()), - ..self } } } @@ -614,8 +632,8 @@ pub(crate) fn generate_token<'a>( generate_token_with_job_name(app_id_source, app_secret_source) } -pub fn authenticate_as_zippy() -> (Step, StepOutput) { - generate_token_with_job_name(vars::ZED_ZIPPY_APP_ID, vars::ZED_ZIPPY_APP_PRIVATE_KEY).into() +pub fn authenticate_as_zippy() -> GenerateAppToken<'static> { + generate_token_with_job_name(vars::ZED_ZIPPY_APP_ID, vars::ZED_ZIPPY_APP_PRIVATE_KEY) } fn generate_token_with_job_name<'a>( @@ -627,5 +645,6 @@ fn generate_token_with_job_name<'a>( app_id: app_id_source, app_secret: app_secret_source, repository_target: None, + permissions: None, } }