mirror of
https://github.com/enso-org/enso.git
synced 2024-09-11 13:15:52 +03:00
Additional info to debug AccessDenied problems (#10663)
Plain `Storage failure [AccessDenied].` was rather uninformative when it comes to debugging the underlying problem. Added more detailed error messages and runners' failures should now sometimes print a detailed message. References #10662.
This commit is contained in:
parent
fde2f71419
commit
0f055c7fb3
12
build.sbt
12
build.sbt
@ -335,6 +335,7 @@ lazy val enso = (project in file("."))
|
||||
`library-manager-test`,
|
||||
`connected-lock-manager`,
|
||||
`connected-lock-manager-server`,
|
||||
`process-utils`,
|
||||
testkit,
|
||||
`test-utils`,
|
||||
`common-polyglot-core-utils`,
|
||||
@ -3137,6 +3138,7 @@ lazy val `library-manager-test` = project
|
||||
)
|
||||
)
|
||||
.dependsOn(`library-manager`)
|
||||
.dependsOn(`process-utils`)
|
||||
.dependsOn(`logging-utils` % "test->test")
|
||||
.dependsOn(testkit)
|
||||
.dependsOn(`logging-service-logback` % "test->test")
|
||||
@ -3189,10 +3191,20 @@ lazy val `runtime-version-manager` = project
|
||||
.dependsOn(pkg)
|
||||
.dependsOn(downloader)
|
||||
.dependsOn(cli)
|
||||
.dependsOn(`process-utils`)
|
||||
.dependsOn(`version-output`)
|
||||
.dependsOn(`edition-updater`)
|
||||
.dependsOn(`distribution-manager`)
|
||||
|
||||
/** `process-utils` provides utilities for correctly managing process execution such as providing
|
||||
* handlers for its stdout/stderr.
|
||||
*/
|
||||
lazy val `process-utils` = project
|
||||
.in(file("lib/scala/process-utils"))
|
||||
.settings(
|
||||
frgaalJavaCompilerSetting
|
||||
)
|
||||
|
||||
lazy val `runtime-version-manager-test` = project
|
||||
.in(file("lib/scala/runtime-version-manager-test"))
|
||||
.configs(Test)
|
||||
|
@ -81,7 +81,7 @@ case class Launcher(cliOptions: GlobalCLIOptions) {
|
||||
versionOverride.getOrElse(configurationManager.defaultVersion)
|
||||
val globalConfig = configurationManager.getConfig
|
||||
|
||||
val exitCode = runner
|
||||
val (exitCode, output) = runner
|
||||
.withCommand(
|
||||
runner
|
||||
.newProject(
|
||||
@ -97,7 +97,7 @@ case class Launcher(cliOptions: GlobalCLIOptions) {
|
||||
.get,
|
||||
JVMSettings(useSystemJVM, jvmOpts, extraOptions = Seq.empty)
|
||||
) { command =>
|
||||
command.run().get
|
||||
command.runAndCaptureOutput().get
|
||||
}
|
||||
|
||||
if (exitCode == 0) {
|
||||
@ -105,7 +105,7 @@ case class Launcher(cliOptions: GlobalCLIOptions) {
|
||||
s"Project created in `$actualPath` using version $version."
|
||||
)
|
||||
} else {
|
||||
logger.error("Project creation failed.")
|
||||
logger.error(s"Project creation failed: $output")
|
||||
}
|
||||
|
||||
exitCode
|
||||
@ -211,7 +211,7 @@ case class Launcher(cliOptions: GlobalCLIOptions) {
|
||||
jvmOpts: Seq[(String, String)],
|
||||
additionalArguments: Seq[String]
|
||||
): Int = {
|
||||
val exitCode = runner
|
||||
runner
|
||||
.withCommand(
|
||||
runner
|
||||
.repl(
|
||||
@ -226,7 +226,6 @@ case class Launcher(cliOptions: GlobalCLIOptions) {
|
||||
) { command =>
|
||||
command.run().get
|
||||
}
|
||||
exitCode
|
||||
}
|
||||
|
||||
/** Runs an Enso script or project.
|
||||
@ -412,12 +411,13 @@ case class Launcher(cliOptions: GlobalCLIOptions) {
|
||||
)
|
||||
.get
|
||||
|
||||
runner.withCommand(
|
||||
settings,
|
||||
JVMSettings(useSystemJVM, jvmOpts, extraOptions = Seq())
|
||||
) { command =>
|
||||
command.run().get
|
||||
}
|
||||
runner
|
||||
.withCommand(
|
||||
settings,
|
||||
JVMSettings(useSystemJVM, jvmOpts, extraOptions = Seq())
|
||||
) { command =>
|
||||
command.run().get
|
||||
}
|
||||
}
|
||||
|
||||
/** Prints the value of `key` from the global configuration.
|
||||
|
@ -2,7 +2,7 @@ package org.enso.launcher
|
||||
|
||||
import org.enso.cli.OS
|
||||
import org.enso.runtimeversionmanager.test.NativeTestHelper
|
||||
import org.enso.testkit.process.RunResult
|
||||
import org.enso.process.RunResult
|
||||
import org.scalatest.concurrent.{Signaler, TimeLimitedTests}
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
import org.scalatest.matchers.{MatchResult, Matcher}
|
||||
|
@ -9,7 +9,7 @@ import FileSystem.PathSyntax
|
||||
import org.enso.cli.OS
|
||||
import org.enso.launcher._
|
||||
import org.enso.testkit.{FlakySpec, WithTemporaryDirectory}
|
||||
import org.enso.testkit.process.{RunResult, WrappedProcess}
|
||||
import org.enso.process.{RunResult, WrappedProcess}
|
||||
import org.scalatest.exceptions.TestFailedException
|
||||
import org.scalatest.{BeforeAndAfterAll, Ignore, OptionValues}
|
||||
|
||||
|
@ -7,7 +7,7 @@ import org.enso.downloader.archive.TarGzWriter
|
||||
import org.enso.editions.Editions.RawEdition
|
||||
import org.enso.editions.{Editions, LibraryName}
|
||||
import org.enso.pkg.{Package, PackageManager}
|
||||
import org.enso.testkit.process.WrappedProcess
|
||||
import org.enso.process.WrappedProcess
|
||||
import org.enso.yaml.YamlHelper
|
||||
|
||||
import java.io.File
|
||||
|
@ -1,4 +1,4 @@
|
||||
package org.enso.testkit.process
|
||||
package org.enso.process
|
||||
|
||||
/** A result of running a process.
|
||||
*
|
@ -1,4 +1,4 @@
|
||||
package org.enso.testkit.process
|
||||
package org.enso.process
|
||||
|
||||
import java.io._
|
||||
import java.util.concurrent.{Semaphore, TimeUnit}
|
||||
@ -87,6 +87,31 @@ class WrappedProcess(command: Seq[String], process: Process) {
|
||||
}
|
||||
}
|
||||
|
||||
/** Registers a handler for stdout/stderr that appends streams to the provided
|
||||
* string builder.
|
||||
*/
|
||||
def registerStringBuilderAppendTarget(
|
||||
builder: StringBuilder
|
||||
): Unit = {
|
||||
def handler(line: String, streamType: StreamType): Unit = {
|
||||
streamType match {
|
||||
case StdErr =>
|
||||
builder.append(
|
||||
s"stderr> $line${System.getProperty("line.separator")}"
|
||||
)
|
||||
case StdOut =>
|
||||
builder.append(
|
||||
s"stdout> $line${System.getProperty("line.separator")}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
this.synchronized {
|
||||
ioHandlers ++= Seq(handler _)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private lazy val inputWriter = new PrintWriter(process.getOutputStream)
|
||||
|
||||
/** Prints a message to the standard input stream of the process.
|
@ -123,12 +123,12 @@ class BlockingFileSystem[F[+_, +_]: Sync: ErrorChannel](
|
||||
.mapError(toFsFailure)
|
||||
|
||||
private val toFsFailure: Throwable => FileSystemFailure = {
|
||||
case _: FileNotFoundException => FileNotFound
|
||||
case _: NotDirectoryException => NotDirectory
|
||||
case _: NoSuchFileException => FileNotFound
|
||||
case _: FileExistsException => FileExists
|
||||
case _: AccessDeniedException => AccessDenied
|
||||
case ex => GenericFileSystemFailure(ex.getMessage)
|
||||
case _: FileNotFoundException => FileNotFound
|
||||
case _: NotDirectoryException => NotDirectory
|
||||
case _: NoSuchFileException => FileNotFound
|
||||
case _: FileExistsException => FileExists
|
||||
case ex: AccessDeniedException => AccessDenied(ex.getFile)
|
||||
case ex => GenericFileSystemFailure(ex.getMessage)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -8,7 +8,9 @@ object FileSystemFailure {
|
||||
|
||||
/** Signals that a user doesn't have access to a file.
|
||||
*/
|
||||
case object AccessDenied extends FileSystemFailure
|
||||
case class AccessDenied(file: String) extends FileSystemFailure {
|
||||
override def toString: String = s"AccessDenied[$file]"
|
||||
}
|
||||
|
||||
/** Signals that the file cannot be found.
|
||||
*/
|
||||
|
@ -66,7 +66,9 @@ class ProjectFileRepository[
|
||||
.recover { case FileNotFound | NotDirectory =>
|
||||
Nil
|
||||
}
|
||||
.mapError(th => StorageFailure(th.toString))
|
||||
.mapError(th =>
|
||||
StorageFailure(s"Cannot find projects at $projectsPath: ${th.toString}")
|
||||
)
|
||||
.flatMap { dirs =>
|
||||
Traverse[List].traverse(dirs)(tryLoadProject).map(_.flatten)
|
||||
}
|
||||
@ -172,14 +174,18 @@ class ProjectFileRepository[
|
||||
): F[ProjectRepositoryFailure, Option[Package[File]]] =
|
||||
Sync[F]
|
||||
.blockingOp { PackageManager.Default.fromDirectory(projectPath) }
|
||||
.mapError(th => StorageFailure(th.toString))
|
||||
.mapError(th => StorageFailure(s"Cannot load package $projectPath: $th"))
|
||||
|
||||
private def getDirectoryCreationTime(
|
||||
pkg: Package[File]
|
||||
): F[ProjectRepositoryFailure, FileTime] =
|
||||
Sync[F]
|
||||
.blockingOp(pkg.fileSystem.getCreationTime(pkg.root))
|
||||
.mapError(th => StorageFailure(th.toString))
|
||||
.mapError(th =>
|
||||
StorageFailure(
|
||||
s"Cannot find creation time for package ${pkg.root}: $th"
|
||||
)
|
||||
)
|
||||
|
||||
private def getPackage(
|
||||
projectPath: File
|
||||
@ -202,14 +208,20 @@ class ProjectFileRepository[
|
||||
Sync[F]
|
||||
.blockingOp { projectPackage.rename(newName) }
|
||||
.map(_ => ())
|
||||
.mapError(th => StorageFailure(th.toString))
|
||||
.mapError(th =>
|
||||
StorageFailure(
|
||||
s"Cannot rename package $newName at $projectPath: $th"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
def update(project: Project): F[ProjectRepositoryFailure, Unit] =
|
||||
metadataStorage(project.path)
|
||||
.persist(ProjectMetadata(project))
|
||||
.mapError(th => StorageFailure(th.toString))
|
||||
.mapError(th =>
|
||||
StorageFailure(s"Cannot update project at ${project.path}: $th")
|
||||
)
|
||||
|
||||
/** @inheritdoc */
|
||||
override def delete(projectId: UUID): F[ProjectRepositoryFailure, Unit] = {
|
||||
@ -218,7 +230,9 @@ class ProjectFileRepository[
|
||||
case Some(project) =>
|
||||
fileSystem
|
||||
.remove(project.path)
|
||||
.mapError(th => StorageFailure(th.toString))
|
||||
.mapError(th =>
|
||||
StorageFailure(s"Cannot delete project at ${project.path}: $th")
|
||||
)
|
||||
case None =>
|
||||
ErrorChannel[F].fail(ProjectNotFoundInIndex)
|
||||
}
|
||||
@ -257,7 +271,11 @@ class ProjectFileRepository[
|
||||
newProjectPath <- copy(project)
|
||||
_ <- metadataStorage(newProjectPath)
|
||||
.persist(newMetadata)
|
||||
.mapError(th => StorageFailure(th.toString))
|
||||
.mapError(th =>
|
||||
StorageFailure(
|
||||
s"Cannot persist new project name at $newProjectPath: $th"
|
||||
)
|
||||
)
|
||||
_ <- renamePackage(newProjectPath, newName)
|
||||
newProject <- getProject(newMetadata.id)
|
||||
} yield newProject
|
||||
@ -267,7 +285,9 @@ class ProjectFileRepository[
|
||||
fileSystem
|
||||
.move(projectPath, targetPath)
|
||||
.mapError[ProjectRepositoryFailure](failure =>
|
||||
StorageFailure(failure.toString)
|
||||
StorageFailure(
|
||||
s"Cannot move project path from $projectPath to $targetPath: $failure"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@ -275,7 +295,9 @@ class ProjectFileRepository[
|
||||
fileSystem
|
||||
.copy(projectPath, targetPath)
|
||||
.mapError[ProjectRepositoryFailure](failure =>
|
||||
StorageFailure(failure.toString)
|
||||
StorageFailure(
|
||||
s"Cannot copy project directory $projectPath to $targetPath: $failure"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@ -292,7 +314,7 @@ class ProjectFileRepository[
|
||||
fileSystem
|
||||
.exists(path)
|
||||
.mapError[ProjectRepositoryFailure](failure =>
|
||||
StorageFailure(failure.toString)
|
||||
StorageFailure(s"Cannot find path $path: $failure")
|
||||
)
|
||||
.flatMap { fileExists =>
|
||||
if (fileExists) {
|
||||
|
@ -74,20 +74,20 @@ class ProjectCreationService[
|
||||
s"Running engine $engineVersion to create project $name at " +
|
||||
s"[${MaskedPath(path).applyMasking()}]."
|
||||
)
|
||||
command.run().get
|
||||
command.runAndCaptureOutput().get
|
||||
}
|
||||
}
|
||||
.mapRuntimeManagerErrors { other: Throwable =>
|
||||
ProjectCreateFailed(other.getMessage)
|
||||
}
|
||||
.flatMap { exitCode =>
|
||||
.flatMap { case (exitCode, output) =>
|
||||
if (exitCode == 0)
|
||||
CovariantFlatMap[F].pure(())
|
||||
else
|
||||
ErrorChannel[F].fail(
|
||||
ProjectCreateFailed(
|
||||
s"The runner used to create the project returned exit code " +
|
||||
s"$exitCode."
|
||||
s"The runner used to create the project returned exit code $exitCode.${System
|
||||
.getProperty("line.separator")}Runner's output: $output"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package org.enso.runtimeversionmanager.test
|
||||
|
||||
import org.enso.cli.OS
|
||||
import org.enso.testkit.process.{RunResult, WrappedProcess}
|
||||
import org.enso.process.{RunResult, WrappedProcess}
|
||||
|
||||
import java.lang.{ProcessBuilder => JProcessBuilder}
|
||||
import scala.jdk.CollectionConverters._
|
||||
|
@ -1,6 +1,7 @@
|
||||
package org.enso.runtimeversionmanager.runner
|
||||
|
||||
import com.typesafe.scalalogging.Logger
|
||||
import org.enso.process.WrappedProcess
|
||||
|
||||
import scala.sys.process.Process
|
||||
import scala.util.{Failure, Try}
|
||||
@ -27,6 +28,31 @@ case class Command(command: Seq[String], extraEnv: Seq[(String, String)]) {
|
||||
process.waitFor()
|
||||
}
|
||||
|
||||
/** Runs the command and returns its exit code along with any output produced..
|
||||
*
|
||||
* May return an exception if it is impossible to run the command (for
|
||||
* example due to insufficient permissions or nonexistent executable).
|
||||
*
|
||||
* @param timeoutInSeconds timeout specifying how long this thread will wait
|
||||
* for the process to finish until it attempts to kill it
|
||||
*/
|
||||
def runAndCaptureOutput(
|
||||
timeoutInSeconds: Option[Long] = Some(300)
|
||||
): Try[(Int, String)] =
|
||||
wrapError {
|
||||
logger.debug("Executing {}", this)
|
||||
val processBuilder = builder()
|
||||
val process = processBuilder.start()
|
||||
val wrappedProcess = new WrappedProcess(command, process)
|
||||
val stringBuilder = new StringBuilder()
|
||||
wrappedProcess.registerStringBuilderAppendTarget(stringBuilder)
|
||||
val result = wrappedProcess.join(
|
||||
waitForDescendants = true,
|
||||
timeoutInSeconds.getOrElse(Long.MaxValue)
|
||||
)
|
||||
(result.exitCode, stringBuilder.toString())
|
||||
}
|
||||
|
||||
/** Runs the command and returns its standard output as [[String]].
|
||||
*
|
||||
* The standard error is printed to the console.
|
||||
|
Loading…
Reference in New Issue
Block a user