ci: Set explicit permissions for Zed Zippy (#52895)

This hopefully fixes the permission issues we are seeing with the Zed
Zippy cherry picks

Release Notes:

- N/A
This commit is contained in:
Finn Evers 2026-04-01 22:04:08 +02:00 committed by GitHub
parent 5a9c763d9a
commit a212304ed2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 88 additions and 46 deletions

View file

@ -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:

View file

@ -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:

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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);

View file

@ -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()])

View file

@ -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<Use> {
named::uses(

View file

@ -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)

View file

@ -513,20 +513,50 @@ pub fn git_checkout(ref_name: &dyn std::fmt::Display) -> Step<Run> {
.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<RepositoryTarget>,
permissions: Option<Vec<(TokenPermissions, Level)>>,
}
impl<'a> GenerateAppToken<'a> {
pub fn for_repository(self, repository_target: RepositoryTarget) -> (Step<Use>, 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<Vec<(TokenPermissions, Level)>>) -> Self {
Self {
permissions: Some(permissions.into()),
..self
}
}
}
@ -549,26 +579,24 @@ impl<'a> From<GenerateAppToken<'a>> for (Step<Use>, 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<GenerateAppToken<'a>> for (Step<Use>, StepOutput) {
pub(crate) struct RepositoryTarget {
owner: Option<String>,
repositories: Option<String>,
permissions: Option<Vec<(String, Level)>>,
}
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<Vec<(String, Level)>>) -> 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<Use>, 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,
}
}