Skip to content

Commit e974029

Browse files
authored
Merge pull request #609 from etspaceman/permissions
Add global permissions
2 parents 2a8780b + e527db9 commit e974029

File tree

6 files changed

+218
-2
lines changed

6 files changed

+218
-2
lines changed

.github/workflows/ci.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,20 @@ on:
1414
branches: ['**', '!update/**', '!pr/**']
1515
tags: [v*]
1616

17+
permissions:
18+
actions: write
19+
checks: write
20+
contents: write
21+
deployments: write
22+
id-token: none
23+
issues: write
24+
packages: write
25+
pages: write
26+
pull-requests: write
27+
repository-projects: write
28+
security-events: write
29+
statuses: write
30+
1731
env:
1832
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
1933

build.sbt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
name := "sbt-typelevel"
22

3+
import org.typelevel.sbt.gha.{PermissionScope, PermissionValue, Permissions}
4+
35
ThisBuild / tlBaseVersion := "0.5"
46
ThisBuild / crossScalaVersions := Seq("2.12.18")
57
ThisBuild / developers ++= List(
@@ -45,6 +47,8 @@ ThisBuild / mergifyPrRules += MergifyPrRule(
4547
)
4648
ThisBuild / mergifyRequiredJobs ++= Seq("validate-steward", "site")
4749

50+
ThisBuild / githubWorkflowPermissions := Some(Permissions.Specify.defaultPermissive)
51+
4852
val MunitVersion = "0.7.29"
4953

5054
lazy val `sbt-typelevel` = tlCrossRootProject.aggregate(

github-actions/src/main/scala/org/typelevel/sbt/gha/GenerativeKeys.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ trait GenerativeKeys {
106106

107107
lazy val githubWorkflowEnv = settingKey[Map[String, String]](
108108
s"A map of static environment variable assignments global to the workflow (default: { GITHUB_TOKEN: $${{ secrets.GITHUB_TOKEN }} })")
109+
lazy val githubWorkflowPermissions = settingKey[Option[Permissions]](
110+
s"Permissions to use for the global workflow (default: None)")
109111
lazy val githubWorkflowAddedJobs = settingKey[Seq[WorkflowJob]](
110112
"A list of additional jobs to add to the CI workflow (default: [])")
111113
}

github-actions/src/main/scala/org/typelevel/sbt/gha/GenerativePlugin.scala

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,49 @@ object GenerativePlugin extends AutoPlugin {
210210
${indent(rendered.mkString("\n"), 1)}"""
211211
}
212212

213+
def compilePermissionScope(permissionScope: PermissionScope): String = permissionScope match {
214+
case PermissionScope.Actions => "actions"
215+
case PermissionScope.Checks => "checks"
216+
case PermissionScope.Contents => "contents"
217+
case PermissionScope.Deployments => "deployments"
218+
case PermissionScope.IdToken => "id-token"
219+
case PermissionScope.Issues => "issues"
220+
case PermissionScope.Discussions => "discussions"
221+
case PermissionScope.Packages => "packages"
222+
case PermissionScope.Pages => "pages"
223+
case PermissionScope.PullRequests => "pull-requests"
224+
case PermissionScope.RepositoryProjects => "repository-projects"
225+
case PermissionScope.SecurityEvents => "security-events"
226+
case PermissionScope.Statuses => "statuses"
227+
}
228+
229+
def compilePermissionsValue(permissionValue: PermissionValue): String =
230+
permissionValue match {
231+
case PermissionValue.Read => "read"
232+
case PermissionValue.Write => "write"
233+
case PermissionValue.None => "none"
234+
}
235+
236+
def compilePermissions(permissions: Option[Permissions]): String = {
237+
permissions match {
238+
case Some(perms) =>
239+
val rendered = perms match {
240+
case Permissions.ReadAll => " read-all"
241+
case Permissions.WriteAll => " write-all"
242+
case Permissions.None => " {}"
243+
case x: Permissions.Specify =>
244+
val map = x.asMap.map {
245+
case (key, value) =>
246+
s"${compilePermissionScope(key)}: ${compilePermissionsValue(value)}"
247+
}
248+
"\n" + indent(map.mkString("\n"), 1)
249+
}
250+
s"permissions:$rendered"
251+
252+
case None => ""
253+
}
254+
}
255+
213256
def compileStep(
214257
step: WorkflowStep,
215258
sbt: String,
@@ -382,6 +425,13 @@ ${indent(rendered.mkString("\n"), 1)}"""
382425
else
383426
"\n" + renderedEnvPre
384427

428+
val renderedPermPre = compilePermissions(job.permissions)
429+
val renderedPerm =
430+
if (renderedPermPre.isEmpty)
431+
""
432+
else
433+
"\n" + renderedPermPre
434+
385435
val renderedTimeoutMinutes =
386436
job.timeoutMinutes.map(timeout => s"\ntimeout-minutes: $timeout").getOrElse("")
387437

@@ -469,7 +519,7 @@ ${indent(rendered.mkString("\n"), 1)}"""
469519
strategy:${renderedFailFast}
470520
matrix:
471521
${buildMatrix(2, "os" -> job.oses, "scala" -> job.scalas, "java" -> job.javas.map(_.render))}${renderedMatrices}
472-
runs-on: ${runsOn}${renderedEnvironment}${renderedContainer}${renderedEnv}${renderedConcurrency}${renderedTimeoutMinutes}
522+
runs-on: ${runsOn}${renderedEnvironment}${renderedContainer}${renderedPerm}${renderedEnv}${renderedConcurrency}${renderedTimeoutMinutes}
473523
steps:
474524
${indent(job.steps.map(compileStep(_, sbt, job.sbtStepPreamble, declareShell = declareShell)).mkString("\n\n"), 1)}"""
475525
// format: on
@@ -492,17 +542,24 @@ ${indent(job.steps.map(compileStep(_, sbt, job.sbtStepPreamble, declareShell = d
492542
tags: List[String],
493543
paths: Paths,
494544
prEventTypes: List[PREventType],
545+
permissions: Option[Permissions],
495546
env: Map[String, String],
496547
concurrency: Option[Concurrency],
497548
jobs: List[WorkflowJob],
498549
sbt: String): String = {
499550

551+
val renderedPermissionsPre = compilePermissions(permissions)
500552
val renderedEnvPre = compileEnv(env)
501553
val renderedEnv =
502554
if (renderedEnvPre.isEmpty)
503555
""
504556
else
505557
renderedEnvPre + "\n\n"
558+
val renderedPerm =
559+
if (renderedPermissionsPre.isEmpty)
560+
""
561+
else
562+
renderedPermissionsPre + "\n\n"
506563

507564
val renderedConcurrency =
508565
concurrency.map(compileConcurrency).map("\n" + _ + "\n\n").getOrElse("")
@@ -545,7 +602,7 @@ on:
545602
push:
546603
branches: [${branches.map(wrap).mkString(", ")}]$renderedTags$renderedPaths
547604

548-
${renderedEnv}${renderedConcurrency}jobs:
605+
${renderedPerm}${renderedEnv}${renderedConcurrency}jobs:
549606
${indent(jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)}
550607
"""
551608
}
@@ -593,6 +650,7 @@ ${indent(jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)}
593650
githubWorkflowTargetTags := Seq(),
594651
githubWorkflowTargetPaths := Paths.None,
595652
githubWorkflowEnv := Map("GITHUB_TOKEN" -> s"$${{ secrets.GITHUB_TOKEN }}"),
653+
githubWorkflowPermissions := None,
596654
githubWorkflowAddedJobs := Seq()
597655
)
598656

@@ -812,6 +870,7 @@ ${indent(jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)}
812870
githubWorkflowTargetTags.value.toList,
813871
githubWorkflowTargetPaths.value,
814872
githubWorkflowPREventTypes.value.toList,
873+
githubWorkflowPermissions.value,
815874
githubWorkflowEnv.value,
816875
githubWorkflowConcurrency.value,
817876
githubWorkflowGeneratedCI.value.toList,
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*
2+
* Copyright 2022 Typelevel
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.typelevel.sbt.gha
18+
19+
import scala.collection.immutable.SortedMap
20+
21+
sealed abstract class Permissions extends Product with Serializable
22+
23+
/**
24+
* @see
25+
* https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs#overview
26+
*/
27+
object Permissions {
28+
case object ReadAll extends Permissions
29+
case object WriteAll extends Permissions
30+
case object None extends Permissions
31+
final case class Specify private (
32+
actions: PermissionValue,
33+
checks: PermissionValue,
34+
contents: PermissionValue,
35+
deployments: PermissionValue,
36+
idToken: PermissionValue,
37+
issues: PermissionValue,
38+
packages: PermissionValue,
39+
pages: PermissionValue,
40+
pullRequests: PermissionValue,
41+
repositoryProjects: PermissionValue,
42+
securityEvents: PermissionValue,
43+
statuses: PermissionValue
44+
) extends Permissions {
45+
private[gha] lazy val asMap: SortedMap[PermissionScope, PermissionValue] = SortedMap(
46+
PermissionScope.Actions -> actions,
47+
PermissionScope.Checks -> checks,
48+
PermissionScope.Contents -> contents,
49+
PermissionScope.Deployments -> deployments,
50+
PermissionScope.IdToken -> idToken,
51+
PermissionScope.Issues -> issues,
52+
PermissionScope.Packages -> packages,
53+
PermissionScope.Pages -> pages,
54+
PermissionScope.PullRequests -> pullRequests,
55+
PermissionScope.RepositoryProjects -> repositoryProjects,
56+
PermissionScope.SecurityEvents -> securityEvents,
57+
PermissionScope.Statuses -> statuses
58+
)
59+
}
60+
object Specify {
61+
// See https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token
62+
val defaultPermissive = Specify(
63+
actions = PermissionValue.Write,
64+
checks = PermissionValue.Write,
65+
contents = PermissionValue.Write,
66+
deployments = PermissionValue.Write,
67+
idToken = PermissionValue.None,
68+
issues = PermissionValue.Write,
69+
packages = PermissionValue.Write,
70+
pages = PermissionValue.Write,
71+
pullRequests = PermissionValue.Write,
72+
repositoryProjects = PermissionValue.Write,
73+
securityEvents = PermissionValue.Write,
74+
statuses = PermissionValue.Write
75+
)
76+
77+
val defaultRestrictive = Specify(
78+
actions = PermissionValue.None,
79+
checks = PermissionValue.None,
80+
contents = PermissionValue.Read,
81+
deployments = PermissionValue.None,
82+
idToken = PermissionValue.None,
83+
issues = PermissionValue.None,
84+
packages = PermissionValue.Read,
85+
pages = PermissionValue.None,
86+
pullRequests = PermissionValue.None,
87+
repositoryProjects = PermissionValue.None,
88+
securityEvents = PermissionValue.None,
89+
statuses = PermissionValue.None
90+
)
91+
92+
val maxPRAccessFromFork = Specify(
93+
actions = PermissionValue.Read,
94+
checks = PermissionValue.Read,
95+
contents = PermissionValue.Read,
96+
deployments = PermissionValue.Read,
97+
idToken = PermissionValue.Read,
98+
issues = PermissionValue.Read,
99+
packages = PermissionValue.Read,
100+
pages = PermissionValue.Read,
101+
pullRequests = PermissionValue.Read,
102+
repositoryProjects = PermissionValue.Read,
103+
securityEvents = PermissionValue.Read,
104+
statuses = PermissionValue.Read
105+
)
106+
}
107+
}
108+
109+
sealed abstract class PermissionScope extends Product with Serializable
110+
111+
object PermissionScope {
112+
case object Actions extends PermissionScope
113+
case object Checks extends PermissionScope
114+
case object Contents extends PermissionScope
115+
case object Deployments extends PermissionScope
116+
case object IdToken extends PermissionScope
117+
case object Issues extends PermissionScope
118+
case object Discussions extends PermissionScope
119+
case object Packages extends PermissionScope
120+
case object Pages extends PermissionScope
121+
case object PullRequests extends PermissionScope
122+
case object RepositoryProjects extends PermissionScope
123+
case object SecurityEvents extends PermissionScope
124+
case object Statuses extends PermissionScope
125+
126+
implicit val permissionScopeOrdering: Ordering[PermissionScope] = (x, y) =>
127+
Ordering[String].compare(x.toString, y.toString)
128+
}
129+
130+
sealed abstract class PermissionValue extends Product with Serializable
131+
132+
object PermissionValue {
133+
case object Read extends PermissionValue
134+
case object Write extends PermissionValue
135+
case object None extends PermissionValue
136+
}

github-actions/src/main/scala/org/typelevel/sbt/gha/WorkflowJob.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ final case class WorkflowJob(
2222
steps: List[WorkflowStep],
2323
sbtStepPreamble: List[String] = List(s"++ $${{ matrix.scala }}"),
2424
cond: Option[String] = None,
25+
permissions: Option[Permissions] = None,
2526
env: Map[String, String] = Map(),
2627
oses: List[String] = List("ubuntu-latest"),
2728
scalas: List[String] = List("2.13"),

0 commit comments

Comments
 (0)