Skip to content

Commit

Permalink
Add permissions globally and to workflow step
Browse files Browse the repository at this point in the history
  • Loading branch information
mdedetrich committed Nov 29, 2022
1 parent caf6c9c commit 781b9f5
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 7 deletions.
1 change: 1 addition & 0 deletions src/main/scala/sbtghactions/GenerativeKeys.scala
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ trait GenerativeKeys {
lazy val githubWorkflowJobSetup = settingKey[Seq[WorkflowStep]]("The automatically-generated checkout, setup, and cache steps which are common to all jobs which touch the build (default: autogenerated)")

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
60 changes: 58 additions & 2 deletions src/main/scala/sbtghactions/GenerativePlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,48 @@ s"""$prefix:
${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 Permissions.Specify(permMap) =>
val map = permMap.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, declareShell: Boolean = false): String = {
import WorkflowStep._

Expand Down Expand Up @@ -331,6 +373,12 @@ ${indent(rendered.mkString("\n"), 1)}"""
else
"\n" + renderedEnvPre

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

List("include", "exclude") foreach { key =>
if (job.matrixAdds.contains(key)) {
sys.error(s"key `$key` is reserved and cannot be used in an Actions matrix definition")
Expand Down Expand Up @@ -407,7 +455,7 @@ strategy:${renderedFailFast}
os:${compileList(job.oses, 3)}
scala:${compileList(job.scalas, 3)}
java:${compileList(job.javas.map(_.render), 3)}${renderedMatrices}
runs-on: ${runsOn}${renderedEnvironment}${renderedContainer}${renderedEnv}
runs-on: ${runsOn}${renderedEnvironment}${renderedContainer}${renderedPerm}${renderedEnv}
steps:
${indent(job.steps.map(compileStep(_, sbt, declareShell = declareShell)).mkString("\n\n"), 1)}"""

Expand All @@ -420,16 +468,22 @@ ${indent(job.steps.map(compileStep(_, sbt, declareShell = declareShell)).mkStrin
tags: List[String],
paths: Paths,
prEventTypes: List[PREventType],
permissions: Option[Permissions],
env: Map[String, String],
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 renderedTypesPre = prEventTypes.map(compilePREventType).mkString("[", ", ", "]")
val renderedTypes = if (prEventTypes.sortBy(_.toString) == PREventType.Defaults)
Expand Down Expand Up @@ -467,7 +521,7 @@ on:
push:
branches: [${branches.map(wrap).mkString(", ")}]$renderedTags$renderedPaths

${renderedEnv}jobs:
${renderedPerm}${renderedEnv}jobs:
${indent(jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)}
"""
}
Expand Down Expand Up @@ -504,6 +558,7 @@ ${indent(jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)}
githubWorkflowTargetPaths := Paths.None,

githubWorkflowEnv := Map("GITHUB_TOKEN" -> s"$${{ secrets.GITHUB_TOKEN }}"),
githubWorkflowPermissions := None,
githubWorkflowAddedJobs := Seq())

private lazy val internalTargetAggregation = settingKey[Seq[File]]("Aggregates target directories from all subprojects")
Expand Down Expand Up @@ -697,6 +752,7 @@ ${indent(jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)}
githubWorkflowTargetTags.value.toList,
githubWorkflowTargetPaths.value,
githubWorkflowPREventTypes.value.toList,
githubWorkflowPermissions.value,
githubWorkflowEnv.value,
githubWorkflowGeneratedCI.value.toList,
sbt)
Expand Down
55 changes: 55 additions & 0 deletions src/main/scala/sbtghactions/PermissionScope.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright 2020-2021 Daniel Spiewak
*
* 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 sbtghactions

sealed trait 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(values: Map[PermissionScope, PermissionValue]) extends Permissions
}

sealed trait 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
}

sealed trait PermissionValue extends Product with Serializable

object PermissionValue {
case object Read extends PermissionValue
case object Write extends PermissionValue
case object None extends PermissionValue
}
1 change: 1 addition & 0 deletions src/main/scala/sbtghactions/WorkflowJob.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ final case class WorkflowJob(
name: String,
steps: List[WorkflowStep],
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.6"),
Expand Down
71 changes: 66 additions & 5 deletions src/test/scala/sbtghactions/GenerativePluginSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class GenerativePluginSpec extends Specification {
|${" " * 2}
|""".stripMargin

compileWorkflow("test", List("main"), Nil, Paths.None, PREventType.Defaults, Map(), Nil, "sbt") mustEqual expected
compileWorkflow("test", List("main"), Nil, Paths.None, PREventType.Defaults, None, Map(), Nil, "sbt") mustEqual expected
}

"produce the appropriate skeleton around a zero-job workflow with non-empty tags" in {
Expand All @@ -64,7 +64,7 @@ class GenerativePluginSpec extends Specification {
|${" " * 2}
|""".stripMargin

compileWorkflow("test", List("main"), List("howdy"), Paths.None, PREventType.Defaults, Map(), Nil, "sbt") mustEqual expected
compileWorkflow("test", List("main"), List("howdy"), Paths.None, PREventType.Defaults, None, Map(), Nil, "sbt") mustEqual expected
}

"respect non-default pr types" in {
Expand All @@ -82,7 +82,7 @@ class GenerativePluginSpec extends Specification {
|${" " * 2}
|""".stripMargin

compileWorkflow("test", List("main"), Nil, Paths.None, List(PREventType.ReadyForReview, PREventType.ReviewRequested, PREventType.Opened), Map(), Nil, "sbt") mustEqual expected
compileWorkflow("test", List("main"), Nil, Paths.None, List(PREventType.ReadyForReview, PREventType.ReviewRequested, PREventType.Opened), None, Map(), Nil, "sbt") mustEqual expected
}

"compile a one-job workflow targeting multiple branch patterns with a environment variables" in {
Expand All @@ -95,6 +95,9 @@ class GenerativePluginSpec extends Specification {
| push:
| branches: [main, backport/v*]
|
|permissions:
| id-token: write
|
|env:
| GITHUB_TOKEN: $${{ secrets.GITHUB_TOKEN }}
|
Expand All @@ -117,6 +120,9 @@ class GenerativePluginSpec extends Specification {
Nil,
Paths.None,
PREventType.Defaults,
Some(Permissions.Specify(Map(
PermissionScope.IdToken -> PermissionValue.Write
))),
Map(
"GITHUB_TOKEN" -> s"$${{ secrets.GITHUB_TOKEN }}"),
List(
Expand Down Expand Up @@ -167,6 +173,7 @@ class GenerativePluginSpec extends Specification {
Nil,
Paths.None,
PREventType.Defaults,
None,
Map(),
List(
WorkflowJob(
Expand Down Expand Up @@ -211,6 +218,7 @@ class GenerativePluginSpec extends Specification {
Nil,
Paths.None,
PREventType.Defaults,
None,
Map(),
List(
WorkflowJob(
Expand Down Expand Up @@ -262,6 +270,7 @@ class GenerativePluginSpec extends Specification {
Nil,
Paths.None,
PREventType.Defaults,
None,
Map(),
List(
WorkflowJob(
Expand Down Expand Up @@ -295,7 +304,7 @@ class GenerativePluginSpec extends Specification {
|${" " * 2}
|""".stripMargin

compileWorkflow("test", List("main"), Nil, Paths.Include(List("**.scala", "**.sbt")), PREventType.Defaults, Map(), Nil, "sbt") mustEqual expected
compileWorkflow("test", List("main"), Nil, Paths.Include(List("**.scala", "**.sbt")), PREventType.Defaults, None, Map(), Nil, "sbt") mustEqual expected
}

"render ignored paths on pull_request and push" in {
Expand All @@ -314,7 +323,7 @@ class GenerativePluginSpec extends Specification {
|${" " * 2}
|""".stripMargin

compileWorkflow("test", List("main"), Nil, Paths.Ignore(List("docs/**")), PREventType.Defaults, Map(), Nil, "sbt") mustEqual expected
compileWorkflow("test", List("main"), Nil, Paths.Ignore(List("docs/**")), PREventType.Defaults, None, Map(), Nil, "sbt") mustEqual expected
}
}

Expand Down Expand Up @@ -588,6 +597,58 @@ class GenerativePluginSpec extends Specification {
- run: csbt ++$${{ matrix.scala }} ci-release"""
}

"compile a job with specific permissions" in {
val results = compileJob(
WorkflowJob(
"publish",
"Publish Release",
List(
WorkflowStep.Sbt(List("ci-release"))),
permissions = Some(
Permissions.Specify(Map(
PermissionScope.IdToken -> PermissionValue.Write
))
)),
"csbt")

results mustEqual s"""publish:
name: Publish Release
strategy:
matrix:
os: [ubuntu-latest]
scala: [2.13.6]
java: [temurin@11]
runs-on: $${{ matrix.os }}
permissions:
id-token: write
steps:
- run: csbt ++$${{ matrix.scala }} ci-release"""
}

"compile a job with read-all permissions" in {
val results = compileJob(
WorkflowJob(
"publish",
"Publish Release",
List(
WorkflowStep.Sbt(List("ci-release"))),
permissions = Some(Permissions.ReadAll)
),
"csbt")

results mustEqual s"""publish:
name: Publish Release
strategy:
matrix:
os: [ubuntu-latest]
scala: [2.13.6]
java: [temurin@11]
runs-on: $${{ matrix.os }}
permissions: read-all
steps:
- run: csbt ++$${{ matrix.scala }} ci-release"""
}

"compile a job with an environment containing a url" in {
val results = compileJob(
WorkflowJob(
Expand Down

0 comments on commit 781b9f5

Please sign in to comment.