Skip to content

Commit

Permalink
Merge pull request #609 from etspaceman/permissions
Browse files Browse the repository at this point in the history
Add global permissions
  • Loading branch information
armanbilge authored Jul 16, 2023
2 parents 2a8780b + e527db9 commit e974029
Show file tree
Hide file tree
Showing 6 changed files with 218 additions and 2 deletions.
14 changes: 14 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,20 @@ on:
branches: ['**', '!update/**', '!pr/**']
tags: [v*]

permissions:
actions: write
checks: write
contents: write
deployments: write
id-token: none
issues: write
packages: write
pages: write
pull-requests: write
repository-projects: write
security-events: write
statuses: write

env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Expand Down
4 changes: 4 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
name := "sbt-typelevel"

import org.typelevel.sbt.gha.{PermissionScope, PermissionValue, Permissions}

ThisBuild / tlBaseVersion := "0.5"
ThisBuild / crossScalaVersions := Seq("2.12.18")
ThisBuild / developers ++= List(
Expand Down Expand Up @@ -45,6 +47,8 @@ ThisBuild / mergifyPrRules += MergifyPrRule(
)
ThisBuild / mergifyRequiredJobs ++= Seq("validate-steward", "site")

ThisBuild / githubWorkflowPermissions := Some(Permissions.Specify.defaultPermissive)

val MunitVersion = "0.7.29"

lazy val `sbt-typelevel` = tlCrossRootProject.aggregate(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ trait GenerativeKeys {

lazy val githubWorkflowEnv = settingKey[Map[String, String]](
s"A map of static environment variable assignments global to the workflow (default: { GITHUB_TOKEN: $${{ secrets.GITHUB_TOKEN }} })")
lazy val githubWorkflowPermissions = settingKey[Option[Permissions]](
s"Permissions to use for the global workflow (default: None)")
lazy val githubWorkflowAddedJobs = settingKey[Seq[WorkflowJob]](
"A list of additional jobs to add to the CI workflow (default: [])")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,49 @@ object GenerativePlugin extends AutoPlugin {
${indent(rendered.mkString("\n"), 1)}"""
}

def compilePermissionScope(permissionScope: PermissionScope): String = permissionScope match {
case PermissionScope.Actions => "actions"
case PermissionScope.Checks => "checks"
case PermissionScope.Contents => "contents"
case PermissionScope.Deployments => "deployments"
case PermissionScope.IdToken => "id-token"
case PermissionScope.Issues => "issues"
case PermissionScope.Discussions => "discussions"
case PermissionScope.Packages => "packages"
case PermissionScope.Pages => "pages"
case PermissionScope.PullRequests => "pull-requests"
case PermissionScope.RepositoryProjects => "repository-projects"
case PermissionScope.SecurityEvents => "security-events"
case PermissionScope.Statuses => "statuses"
}

def compilePermissionsValue(permissionValue: PermissionValue): String =
permissionValue match {
case PermissionValue.Read => "read"
case PermissionValue.Write => "write"
case PermissionValue.None => "none"
}

def compilePermissions(permissions: Option[Permissions]): String = {
permissions match {
case Some(perms) =>
val rendered = perms match {
case Permissions.ReadAll => " read-all"
case Permissions.WriteAll => " write-all"
case Permissions.None => " {}"
case x: Permissions.Specify =>
val map = x.asMap.map {
case (key, value) =>
s"${compilePermissionScope(key)}: ${compilePermissionsValue(value)}"
}
"\n" + indent(map.mkString("\n"), 1)
}
s"permissions:$rendered"

case None => ""
}
}

def compileStep(
step: WorkflowStep,
sbt: String,
Expand Down Expand Up @@ -382,6 +425,13 @@ ${indent(rendered.mkString("\n"), 1)}"""
else
"\n" + renderedEnvPre

val renderedPermPre = compilePermissions(job.permissions)
val renderedPerm =
if (renderedPermPre.isEmpty)
""
else
"\n" + renderedPermPre

val renderedTimeoutMinutes =
job.timeoutMinutes.map(timeout => s"\ntimeout-minutes: $timeout").getOrElse("")

Expand Down Expand Up @@ -469,7 +519,7 @@ ${indent(rendered.mkString("\n"), 1)}"""
strategy:${renderedFailFast}
matrix:
${buildMatrix(2, "os" -> job.oses, "scala" -> job.scalas, "java" -> job.javas.map(_.render))}${renderedMatrices}
runs-on: ${runsOn}${renderedEnvironment}${renderedContainer}${renderedEnv}${renderedConcurrency}${renderedTimeoutMinutes}
runs-on: ${runsOn}${renderedEnvironment}${renderedContainer}${renderedPerm}${renderedEnv}${renderedConcurrency}${renderedTimeoutMinutes}
steps:
${indent(job.steps.map(compileStep(_, sbt, job.sbtStepPreamble, declareShell = declareShell)).mkString("\n\n"), 1)}"""
// format: on
Expand All @@ -492,17 +542,24 @@ ${indent(job.steps.map(compileStep(_, sbt, job.sbtStepPreamble, declareShell = d
tags: List[String],
paths: Paths,
prEventTypes: List[PREventType],
permissions: Option[Permissions],
env: Map[String, String],
concurrency: Option[Concurrency],
jobs: List[WorkflowJob],
sbt: String): String = {

val renderedPermissionsPre = compilePermissions(permissions)
val renderedEnvPre = compileEnv(env)
val renderedEnv =
if (renderedEnvPre.isEmpty)
""
else
renderedEnvPre + "\n\n"
val renderedPerm =
if (renderedPermissionsPre.isEmpty)
""
else
renderedPermissionsPre + "\n\n"

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

${renderedEnv}${renderedConcurrency}jobs:
${renderedPerm}${renderedEnv}${renderedConcurrency}jobs:
${indent(jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)}
"""
}
Expand Down Expand Up @@ -593,6 +650,7 @@ ${indent(jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)}
githubWorkflowTargetTags := Seq(),
githubWorkflowTargetPaths := Paths.None,
githubWorkflowEnv := Map("GITHUB_TOKEN" -> s"$${{ secrets.GITHUB_TOKEN }}"),
githubWorkflowPermissions := None,
githubWorkflowAddedJobs := Seq()
)

Expand Down Expand Up @@ -812,6 +870,7 @@ ${indent(jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)}
githubWorkflowTargetTags.value.toList,
githubWorkflowTargetPaths.value,
githubWorkflowPREventTypes.value.toList,
githubWorkflowPermissions.value,
githubWorkflowEnv.value,
githubWorkflowConcurrency.value,
githubWorkflowGeneratedCI.value.toList,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* Copyright 2022 Typelevel
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.typelevel.sbt.gha

import scala.collection.immutable.SortedMap

sealed abstract class Permissions extends Product with Serializable

/**
* @see
* https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs#overview
*/
object Permissions {
case object ReadAll extends Permissions
case object WriteAll extends Permissions
case object None extends Permissions
final case class Specify private (
actions: PermissionValue,
checks: PermissionValue,
contents: PermissionValue,
deployments: PermissionValue,
idToken: PermissionValue,
issues: PermissionValue,
packages: PermissionValue,
pages: PermissionValue,
pullRequests: PermissionValue,
repositoryProjects: PermissionValue,
securityEvents: PermissionValue,
statuses: PermissionValue
) extends Permissions {
private[gha] lazy val asMap: SortedMap[PermissionScope, PermissionValue] = SortedMap(
PermissionScope.Actions -> actions,
PermissionScope.Checks -> checks,
PermissionScope.Contents -> contents,
PermissionScope.Deployments -> deployments,
PermissionScope.IdToken -> idToken,
PermissionScope.Issues -> issues,
PermissionScope.Packages -> packages,
PermissionScope.Pages -> pages,
PermissionScope.PullRequests -> pullRequests,
PermissionScope.RepositoryProjects -> repositoryProjects,
PermissionScope.SecurityEvents -> securityEvents,
PermissionScope.Statuses -> statuses
)
}
object Specify {
// See https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token
val defaultPermissive = Specify(
actions = PermissionValue.Write,
checks = PermissionValue.Write,
contents = PermissionValue.Write,
deployments = PermissionValue.Write,
idToken = PermissionValue.None,
issues = PermissionValue.Write,
packages = PermissionValue.Write,
pages = PermissionValue.Write,
pullRequests = PermissionValue.Write,
repositoryProjects = PermissionValue.Write,
securityEvents = PermissionValue.Write,
statuses = PermissionValue.Write
)

val defaultRestrictive = Specify(
actions = PermissionValue.None,
checks = PermissionValue.None,
contents = PermissionValue.Read,
deployments = PermissionValue.None,
idToken = PermissionValue.None,
issues = PermissionValue.None,
packages = PermissionValue.Read,
pages = PermissionValue.None,
pullRequests = PermissionValue.None,
repositoryProjects = PermissionValue.None,
securityEvents = PermissionValue.None,
statuses = PermissionValue.None
)

val maxPRAccessFromFork = Specify(
actions = PermissionValue.Read,
checks = PermissionValue.Read,
contents = PermissionValue.Read,
deployments = PermissionValue.Read,
idToken = PermissionValue.Read,
issues = PermissionValue.Read,
packages = PermissionValue.Read,
pages = PermissionValue.Read,
pullRequests = PermissionValue.Read,
repositoryProjects = PermissionValue.Read,
securityEvents = PermissionValue.Read,
statuses = PermissionValue.Read
)
}
}

sealed abstract class PermissionScope extends Product with Serializable

object PermissionScope {
case object Actions extends PermissionScope
case object Checks extends PermissionScope
case object Contents extends PermissionScope
case object Deployments extends PermissionScope
case object IdToken extends PermissionScope
case object Issues extends PermissionScope
case object Discussions extends PermissionScope
case object Packages extends PermissionScope
case object Pages extends PermissionScope
case object PullRequests extends PermissionScope
case object RepositoryProjects extends PermissionScope
case object SecurityEvents extends PermissionScope
case object Statuses extends PermissionScope

implicit val permissionScopeOrdering: Ordering[PermissionScope] = (x, y) =>
Ordering[String].compare(x.toString, y.toString)
}

sealed abstract class PermissionValue extends Product with Serializable

object PermissionValue {
case object Read extends PermissionValue
case object Write extends PermissionValue
case object None extends PermissionValue
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ final case class WorkflowJob(
steps: List[WorkflowStep],
sbtStepPreamble: List[String] = List(s"++ $${{ matrix.scala }}"),
cond: Option[String] = None,
permissions: Option[Permissions] = None,
env: Map[String, String] = Map(),
oses: List[String] = List("ubuntu-latest"),
scalas: List[String] = List("2.13"),
Expand Down

0 comments on commit e974029

Please sign in to comment.