diff --git a/cli/src/main/scala/acab/devcon0/dtos/FederationMemberUpdateInputVariables.scala b/cli/src/main/scala/acab/devcon0/dtos/FederationMemberUpdateInputVariables.scala index 9c5f287fd37fcfb32ba1b5308391cf428821fc3f..7580c214842fabe5a7b4c72cb1de1cbd037da03b 100644 --- a/cli/src/main/scala/acab/devcon0/dtos/FederationMemberUpdateInputVariables.scala +++ b/cli/src/main/scala/acab/devcon0/dtos/FederationMemberUpdateInputVariables.scala @@ -1,7 +1,3 @@ -package acab.devcon0.dtosFederationMemberUpdateInputVariables - -import acab.devcon0.dtos.TrileCli.Environment - -import java.net.InetAddress +package acab.devcon0.dtos case class FederationMemberUpdateInputVariables(federationName: String, nickname: String) diff --git a/cli/src/main/scala/acab/devcon0/program/commands/InstallFederationControllerCommand.scala b/cli/src/main/scala/acab/devcon0/program/commands/InstallFederationControllerCommand.scala index e4ffd99dea21ef173ea3bf602ac6837e572f46ab..625e9932ad3433fa66a69b77773039075e64882e 100644 --- a/cli/src/main/scala/acab/devcon0/program/commands/InstallFederationControllerCommand.scala +++ b/cli/src/main/scala/acab/devcon0/program/commands/InstallFederationControllerCommand.scala @@ -3,9 +3,14 @@ package acab.devcon0.program.commands import acab.devcon0.dtos.FederationControllerInstallInputVariables import acab.devcon0.dtos.TrileCli.Environment import acab.devcon0.dtos.TrileCli.Environment.* +import acab.devcon0.program.commands.InstallFederationMemberCommand.runtime import acab.devcon0.program.opts.{TrileOptsFederation, TrileOptsFederationController} -import acab.devcon0.services.command.installer.InstallControllerCommandHandler +import acab.devcon0.services.command.install.{InstallControllerCommandHandler, InstallMemberCommandHandler} +import acab.devcon0.services.shell.ShellExecutor.attemptTapPrintProgressFinal import acab.devcon0.services.shell.{Clear, PrintLn} +import cats.effect.IO +import cats.effect.std.Supervisor +import cats.effect.unsafe.IORuntime import cats.implicits.* import com.monovore.decline.* @@ -13,6 +18,8 @@ import scala.io.StdIn object InstallFederationControllerCommand { + private implicit val runtime: IORuntime = cats.effect.unsafe.IORuntime.global + def apply(): Command[Unit] = Command(name = "controller", header = "Run the installation for a controller.") { ( @@ -44,7 +51,7 @@ object InstallFederationControllerCommand { environment, federationName, networkAddress, - generateSSL = certbotFolderAbsolutePathOptional.isEmpty, + generateSSL = environment.equals(Environment.Production) && certbotFolderAbsolutePathOptional.isEmpty, stagingSSL = if sslStaging then "1" else "0", certbotFolderAbsolutePathOptional = certbotFolderAbsolutePathOptional, ipfsClusterReplicaFactorMax, @@ -65,11 +72,17 @@ object InstallFederationControllerCommand { private def triggerInstallation( federationControllerInstallInputVariables: FederationControllerInstallInputVariables ): Unit = { - println("") - println("Creating the configuration & boot the containers, good luck!") - InstallControllerCommandHandler(federationControllerInstallInputVariables) - println("") - PrintLn.green("Installation completed. Enjoy.") + Supervisor[IO](await = true) + .use(supervisor => { + for + _ <- IO(println("Up to install the controller, good luck!")) + _ <- IO(println()) + _ <- InstallControllerCommandHandler(federationControllerInstallInputVariables) + yield () + }) + .attemptTap(attemptTapPrintProgressFinal("Controller installation", _)) + .unsafeToFuture()(runtime) + .wait() } private def printInputVariables(inputVariables: FederationControllerInstallInputVariables): Unit = { diff --git a/cli/src/main/scala/acab/devcon0/program/commands/InstallFederationMemberCommand.scala b/cli/src/main/scala/acab/devcon0/program/commands/InstallFederationMemberCommand.scala index 2a1f82bccc6e7eb382f9d78dc68b4c296fb6ee6e..3d8729c8d0731003ade86e56082a2d3aa6aa5b70 100644 --- a/cli/src/main/scala/acab/devcon0/program/commands/InstallFederationMemberCommand.scala +++ b/cli/src/main/scala/acab/devcon0/program/commands/InstallFederationMemberCommand.scala @@ -4,10 +4,16 @@ import acab.devcon0.dtos.FederationMemberInstallInputVariables import acab.devcon0.dtos.TrileCli.Environment import acab.devcon0.dtos.TrileCli.Environment.* import acab.devcon0.dtos.TrileCli.EnvironmentVariables.* +import acab.devcon0.program.commands.UpdateAllCommand.runtime import acab.devcon0.program.opts.{TrileOptsFederation, TrileOptsFederationMember} import acab.devcon0.services.EnvVarsFileLoader -import acab.devcon0.services.command.installer.InstallMemberCommandHandler +import acab.devcon0.services.command.install.InstallMemberCommandHandler +import acab.devcon0.services.command.update.UpdateFederationCommandHandler +import acab.devcon0.services.shell.ShellExecutor.attemptTapPrintProgressFinal import acab.devcon0.services.shell.{Clear, PrintLn} +import cats.effect.IO +import cats.effect.std.Supervisor +import cats.effect.unsafe.IORuntime import cats.implicits.* import com.monovore.decline.* @@ -16,6 +22,8 @@ import scala.util.Try object InstallFederationMemberCommand { + private implicit val runtime: IORuntime = cats.effect.unsafe.IORuntime.global + def apply(): Command[Unit] = Command(name = "member", header = "Run the installation for a member.") { ( @@ -66,14 +74,20 @@ object InstallFederationMemberCommand { fedvarsMap: TrileFederationEnvVars, federationMemberInstallInputVariables: FederationMemberInstallInputVariables ): Unit = { - println("") - println("Create the configuration & boot the containers, good luck!") - InstallMemberCommandHandler( - inputVariables = federationMemberInstallInputVariables, - fedvarsAsMap = fedvarsMap - ) - println("") - PrintLn.green("Installation completed. Enjoy.") + Supervisor[IO](await = true) + .use(supervisor => { + for + _ <- IO(println("Up to install the member, good luck!")) + _ <- IO(println()) + _ <- InstallMemberCommandHandler( + inputVariables = federationMemberInstallInputVariables, + fedvarsAsMap = fedvarsMap + ) + yield () + }) + .attemptTap(attemptTapPrintProgressFinal("Member installation", _)) + .unsafeToFuture()(runtime) + .wait() } private def printInputVariables(inputVariables: FederationMemberInstallInputVariables): Unit = { diff --git a/cli/src/main/scala/acab/devcon0/program/commands/UpdateAllCommand.scala b/cli/src/main/scala/acab/devcon0/program/commands/UpdateAllCommand.scala index 03509a32f57c9b4108cfe2ae6da27081b7744024..46ba344176182e9f200ae7c960a2c635b16df5fe 100644 --- a/cli/src/main/scala/acab/devcon0/program/commands/UpdateAllCommand.scala +++ b/cli/src/main/scala/acab/devcon0/program/commands/UpdateAllCommand.scala @@ -2,15 +2,21 @@ package acab.devcon0.program.commands import acab.devcon0.dtos.FederationUpdateInputVariables import acab.devcon0.program.opts.{TrileOptsFederation, TrileOptsFederationController, TrileOptsFederationMember} -import acab.devcon0.services.command.installer.UpdateFederationCommandHandler -import acab.devcon0.services.shell.{Clear, PrintLn} -import cats.implicits.catsSyntaxTuple2Semigroupal +import acab.devcon0.services.command.update.UpdateFederationCommandHandler +import acab.devcon0.services.shell.ShellExecutor.attemptTapPrintProgressFinal +import acab.devcon0.services.shell.{Clear, PrintLn, ShellExecutor} +import cats.effect.IO +import cats.effect.std.Supervisor +import cats.effect.unsafe.IORuntime +import cats.implicits.{catsSyntaxMonadError, catsSyntaxTuple2Semigroupal} import com.monovore.decline.* import scala.io.StdIn object UpdateAllCommand { + private implicit val runtime: IORuntime = cats.effect.unsafe.IORuntime.global + def apply(): Command[Unit] = Command(name = "all", header = "Updates controllers & members present in the machine.") { ( @@ -37,12 +43,18 @@ object UpdateAllCommand { val inputVariables: FederationUpdateInputVariables = FederationUpdateInputVariables( federationName = federationName ) - printInputVariables(inputVariables) - println("") - println("Up to update the controller and every member, good luck!") - UpdateFederationCommandHandler(inputVariables) - println("") - PrintLn.green("Update completed. Enjoy.") + + Supervisor[IO](await = true) + .use(supervisor => { + for + _ <- IO(println("Up to update the controller and every member, good luck!")) + _ <- IO(println()) + _ <- UpdateFederationCommandHandler(inputVariables) + yield () + }) + .attemptTap(attemptTapPrintProgressFinal("Federation update", _)) + .unsafeToFuture()(runtime) + .wait() } private def printInputVariables(inputVariables: FederationUpdateInputVariables): Unit = { @@ -55,10 +67,7 @@ object UpdateAllCommand { private def greet(): Unit = { Clear() PrintLn.cyan(s"Welcome to trile! You are up to update a federation.") - PrintLn.cyan( - s"You can pass your values as arguments. use 'update controller all --help' to see the list. " + - s"In order to complete the installation, all values need to be provided." - ) + PrintLn.cyan("Check 'update controller --help' to see the list of options.") } private def readConfirmation(): Boolean = { diff --git a/cli/src/main/scala/acab/devcon0/program/commands/UpdateFederationControllerCommand.scala b/cli/src/main/scala/acab/devcon0/program/commands/UpdateFederationControllerCommand.scala index 5a2aa4182ad46efde94aa5576627314367ee5532..58c765c6c1bfe7d0d576bf9a999eac50a4dc952c 100644 --- a/cli/src/main/scala/acab/devcon0/program/commands/UpdateFederationControllerCommand.scala +++ b/cli/src/main/scala/acab/devcon0/program/commands/UpdateFederationControllerCommand.scala @@ -1,16 +1,21 @@ package acab.devcon0.program.commands import acab.devcon0.dtos.FederationControllerUpdateInputVariables -import acab.devcon0.program.opts.{TrileOptsFederation, TrileOptsFederationController, TrileOptsFederationMember} -import acab.devcon0.services.command.installer.UpdateControllerCommandHandler -import acab.devcon0.services.shell.{Clear, PrintLn} -import cats.implicits.catsSyntaxTuple2Semigroupal +import acab.devcon0.program.opts.TrileOptsFederation +import acab.devcon0.services.command.update.UpdateControllerCommandHandler +import acab.devcon0.services.shell.{Clear, PrintLn, ShellExecutor} +import cats.effect.IO +import cats.effect.std.Supervisor +import cats.effect.unsafe.IORuntime +import cats.implicits.{catsSyntaxMonadError, catsSyntaxTuple2Semigroupal} import com.monovore.decline.* import scala.io.StdIn object UpdateFederationControllerCommand { + private implicit val runtime: IORuntime = cats.effect.unsafe.IORuntime.global + def apply(): Command[Unit] = Command(name = "controller", header = "Updates a controller installation.") { ( @@ -39,11 +44,17 @@ object UpdateFederationControllerCommand { } private def triggerUpdate(inputVariables: FederationControllerUpdateInputVariables): Unit = { - println("") - println("Updating the configuration & boot the containers, good luck!") - UpdateControllerCommandHandler(inputVariables) - println("") - PrintLn.green("Update completed. Enjoy.") + Supervisor[IO](await = true) + .use(supervisor => { + for + _ <- IO(println("Update is up to start. Good luck!")) + _ <- IO(println()) + _ <- UpdateControllerCommandHandler(inputVariables) + yield () + }) + .attemptTap(ShellExecutor.attemptTapPrintProgressFinal("Controller update", _)) + .unsafeToFuture()(runtime) + .wait() } private def printInputVariables(inputVariables: FederationControllerUpdateInputVariables): Unit = { @@ -56,10 +67,7 @@ object UpdateFederationControllerCommand { private def greet(): Unit = { Clear() PrintLn.cyan(s"Welcome to trile! You are up to update a federation controller.") - PrintLn.cyan( - s"You can pass your values as arguments. use 'update controller --help' to see the list. " + - s"In order to complete the installation, all values need to be provided." - ) + PrintLn.cyan("Check 'update controller --help' to see the list of options.") } private def readConfirmation(): Boolean = { diff --git a/cli/src/main/scala/acab/devcon0/program/commands/UpdateFederationMemberCommand.scala b/cli/src/main/scala/acab/devcon0/program/commands/UpdateFederationMemberCommand.scala index cc755edf032a9be2368a2379550f5b161187b0e1..09fa9a5b8b77b83d4087a9aa09350ee228c58015 100644 --- a/cli/src/main/scala/acab/devcon0/program/commands/UpdateFederationMemberCommand.scala +++ b/cli/src/main/scala/acab/devcon0/program/commands/UpdateFederationMemberCommand.scala @@ -1,10 +1,12 @@ package acab.devcon0.program.commands -import acab.devcon0.dtosFederationMemberUpdateInputVariables.FederationMemberUpdateInputVariables -import acab.devcon0.program.commands.UpdateFederationControllerCommand.{readConfirmation, triggerUpdate} +import acab.devcon0.dtos.FederationMemberUpdateInputVariables import acab.devcon0.program.opts.{TrileOptsFederation, TrileOptsFederationMember} -import acab.devcon0.services.command.installer.UpdateMemberCommandHandler -import acab.devcon0.services.shell.{Clear, PrintLn} +import acab.devcon0.services.command.update.UpdateMemberCommandHandler +import acab.devcon0.services.shell.{Clear, PrintLn, ShellExecutor} +import cats.effect.IO +import cats.effect.std.Supervisor +import cats.effect.unsafe.IORuntime import cats.implicits.catsSyntaxTuple3Semigroupal import com.monovore.decline.* @@ -12,6 +14,8 @@ import scala.io.StdIn object UpdateFederationMemberCommand { + private implicit val runtime: IORuntime = cats.effect.unsafe.IORuntime.global + def apply(): Command[Unit] = Command(name = "member", header = "Updates a member installation.") { ( @@ -43,16 +47,24 @@ object UpdateFederationMemberCommand { } private def triggerUpdate(inputVariables: FederationMemberUpdateInputVariables): Unit = { - println("") - println("Updating the configuration & boot the containers, good luck!") - UpdateMemberCommandHandler(inputVariables) - println("") - PrintLn.green("Update completed. Enjoy.") + Supervisor[IO](await = true) + .use(supervisor => { + for + _ <- IO(println("Update is up to start. Good luck!")) + _ <- IO(println()) + _ <- UpdateMemberCommandHandler(inputVariables) + _ <- IO(println()) + _ <- ShellExecutor.printProgressOk("Update") + yield () + }) + .handleErrorWith(_ => ShellExecutor.printProgressError("Member update")) + .unsafeToFuture()(runtime) + .wait() } private def printInputVariables(inputVariables: FederationMemberUpdateInputVariables): Unit = { println() - println(s"These are you input values. Please review them: ") + println(s"These are you input values") println(s"- Federation name: ${inputVariables.federationName}") println(s"- Nickname: ${inputVariables.nickname}") println() @@ -61,10 +73,7 @@ object UpdateFederationMemberCommand { private def greet(): Unit = { Clear() PrintLn.cyan(s"Welcome to trile! You are up to update a federation member.") - PrintLn.cyan( - s"You can pass your values as arguments. use 'update controller --help' to see the list. " + - s"In order to complete the installation, all values need to be provided." - ) + PrintLn.cyan("Check 'update controller --help' to see the list of options.") } private def readConfirmation(): Boolean = { diff --git a/cli/src/main/scala/acab/devcon0/services/FederationControllerService.scala b/cli/src/main/scala/acab/devcon0/services/FederationControllerService.scala index 00e005a9e524546afa92237712adf44fb1adabed..74d2072bbbeb8708d4652513ba5547d9e5d34646 100644 --- a/cli/src/main/scala/acab/devcon0/services/FederationControllerService.scala +++ b/cli/src/main/scala/acab/devcon0/services/FederationControllerService.scala @@ -3,7 +3,9 @@ package acab.devcon0.services import acab.devcon0.dtos.TrileCli import acab.devcon0.dtos.TrileCli.Environment import acab.devcon0.dtos.aliases.P2pPeerId +import acab.devcon0.services.shell.ShellExecutor import cats.effect.IO +import cats.implicits.catsSyntaxMonadError import sttp.client4.curl.CurlBackend import sttp.client4.{SyncBackend, UriContext, quickRequest} import sttp.model.{StatusCode, Uri} @@ -17,6 +19,7 @@ object FederationControllerService { def getPeerId(environment: Environment, networkAddress: P2pPeerId): IO[P2pPeerId] = { val uri: Uri = getUri(environment, networkAddress) getP2pPeerIdInner(uri, 300) + .attemptTap(ShellExecutor.attemptTapPrintProgressFinal("Fetching P2P peer id", _)) } private def getUri(environment: Environment, networkAddress: P2pPeerId): Uri = { diff --git a/cli/src/main/scala/acab/devcon0/services/command/installer/InstallControllerCommandHandler.scala b/cli/src/main/scala/acab/devcon0/services/command/install/InstallControllerCommandHandler.scala similarity index 83% rename from cli/src/main/scala/acab/devcon0/services/command/installer/InstallControllerCommandHandler.scala rename to cli/src/main/scala/acab/devcon0/services/command/install/InstallControllerCommandHandler.scala index 72af4804245b9704cfe4b249ec65e3ae4390dd10..1b0e0f995d30584a9489a735bb6373f1e27fb93c 100644 --- a/cli/src/main/scala/acab/devcon0/services/command/installer/InstallControllerCommandHandler.scala +++ b/cli/src/main/scala/acab/devcon0/services/command/install/InstallControllerCommandHandler.scala @@ -1,4 +1,4 @@ -package acab.devcon0.services.command.installer +package acab.devcon0.services.command.install import acab.devcon0.dtos.* import acab.devcon0.dtos.TrileCli.Component.* @@ -8,7 +8,7 @@ import acab.devcon0.dtos.TrileCli.{Component, Defaults, Environment, File} import acab.devcon0.services.shell.* import acab.devcon0.services.{FederationControllerEnvironmentVariables, FederationControllerService} import acab.devcon0.{files, services} -import cats.effect.unsafe.IORuntime +import cats.effect.IO import ujson.Value.Value import java.nio.charset.StandardCharsets @@ -17,29 +17,30 @@ import scala.io.Source object InstallControllerCommandHandler { - private implicit val runtime: IORuntime = cats.effect.unsafe.IORuntime.global - - def apply(inputVariables: FederationControllerInstallInputVariables): Unit = { + def apply(inputVariables: FederationControllerInstallInputVariables): IO[Unit] = { val environmentVariables: TrileFederationEnvVars = FederationControllerEnvironmentVariables(inputVariables) val configurationFolderAbsolutePath = getConfigurationFolderAbsolutePath(environmentVariables) val dockerComposeAbsolutePath = getDockerComposeAbsolutePath(configurationFolderAbsolutePath) - generateConfigurationFiles(inputVariables, environmentVariables) - DockerComposeUp(dockerComposeAbsolutePath) - if inputVariables.generateSSL then generateSslCertificate(environmentVariables) - generateConfigurationFilesPostSsl(inputVariables, environmentVariables) - DockerComposeUp(dockerComposeAbsolutePath) - generateFedVars(inputVariables, environmentVariables) - generateInstallationEnvVars(environmentVariables) + generateConfigurationFiles(inputVariables, environmentVariables) >> + DockerComposeUp(dockerComposeAbsolutePath) >> + IO.unit.flatMap(_ => + if inputVariables.generateSSL then generateSslCertificate(environmentVariables) + else IO.unit + ) >> + generateConfigurationFilesPostSsl(inputVariables, environmentVariables) >> + DockerComposeUp(dockerComposeAbsolutePath) >> + generateFedVars(inputVariables, environmentVariables) >> + generateInstallationEnvVars(environmentVariables) } private def generateFedVars( inputVariables: FederationControllerInstallInputVariables, environmentVariablesMap: TrileFederationEnvVars - ): Unit = { + ): IO[Unit] = { val networkAddress: String = environmentVariablesMap(TFC_NETWORK_ADDRESS) val environment: Environment = inputVariables.environment - (for peerId <- FederationControllerService.getPeerId(environment, networkAddress) + for peerId <- FederationControllerService.getPeerId(environment, networkAddress) yield { val configurationFolderAbsolutePath = getConfigurationFolderAbsolutePath(environmentVariablesMap) val fedvarsAbsolutePath = s"$configurationFolderAbsolutePath/.fedvars" @@ -59,10 +60,10 @@ object InstallControllerCommandHandler { val fedVarsContent: String = map.view.toList.map { case (key, value) => s"$key=$value" }.mkString("\n") Files.write(Paths.get(fedvarsAbsolutePath), fedVarsContent.getBytes(StandardCharsets.UTF_8)) - }).unsafeToFuture()(runtime).wait() + } } - private def generateInstallationEnvVars(envVars: TrileFederationEnvVars): Unit = { + private def generateInstallationEnvVars(envVars: TrileFederationEnvVars): IO[Unit] = IO { val configurationFolderAbsolutePath = getConfigurationFolderAbsolutePath(envVars) val installationEnvVarsAbsolutePath = s"$configurationFolderAbsolutePath/.installationEnvVars" @@ -117,7 +118,7 @@ object InstallControllerCommandHandler { private def generateConfigurationFiles( inputVariables: FederationControllerInstallInputVariables, envVars: TrileFederationEnvVars - ): Unit = { + ): IO[Unit] = { val environment = inputVariables.environment val configFolderAbsolutePath = getConfigurationFolderAbsolutePath(envVars) val ipfsConfigurationFolderAbsolutePath = getIpfsConfigurationFolderAbsolutePath(envVars) @@ -135,30 +136,30 @@ object InstallControllerCommandHandler { val nginxPreSslConfTemplateContent = files.Loader.get(NginxPreSslConf, FederationController, environment) val sslCertificateGeneratorTemplateContent = files.Loader.get(SslCertificate, FederationController, environment) - SudoRm(configFolderAbsolutePath) - MkdirP(configFolderAbsolutePath) - MkdirP(certbotConfigurationFolderAbsolutePath) - MkdirP(nginxFolderAbsolutePath) - MkdirP(ipfsConfigurationFolderAbsolutePath) - MkdirP(ipfsClusterConfigurationFolderAbsolutePath) - IpfsGenerateSwarmKey(swarmKeyAbsolutePath) - - val envVarsPreBoot = envVars - .updated(TFC_REVERSE_PROXY_CONFIGURATION, nginxPreSslConfigurationFileAbsolutePath) - .updated(TRILE_FEDERATION_IPFS_SWARM_KEY_VALUE, getIpfsSwarmKeyValue(envVars)) - - EnvSust(envVars, dockerComposeTemplateContent, dockerComposeAbsolutePath) - EnvSust(envVars, nginxConfTemplateContent, nginxConfigurationFileAbsolutePath) - EnvSust(envVars, nginxPreSslConfTemplateContent, nginxPreSslConfigurationFileAbsolutePath) - EnvSust(envVars, sslCertificateGeneratorTemplateContent, sslCertificateGeneratorFileAbsolutePath) - EnvSust(envVarsPreBoot, dockerComposeTemplateContent, dockerComposeAbsolutePath) - ChmodX(sslCertificateGeneratorFileAbsolutePath) + for + _ <- SudoRm(configFolderAbsolutePath) + _ <- MkdirP(configFolderAbsolutePath) + _ <- MkdirP(certbotConfigurationFolderAbsolutePath) + _ <- MkdirP(nginxFolderAbsolutePath) + _ <- MkdirP(ipfsConfigurationFolderAbsolutePath) + _ <- MkdirP(ipfsClusterConfigurationFolderAbsolutePath) + _ <- IpfsGenerateSwarmKey(swarmKeyAbsolutePath) + envVarsPreBoot = envVars + .updated(TFC_REVERSE_PROXY_CONFIGURATION, nginxPreSslConfigurationFileAbsolutePath) + .updated(TRILE_FEDERATION_IPFS_SWARM_KEY_VALUE, getIpfsSwarmKeyValue(envVars)) + _ <- EnvSust(envVars, dockerComposeTemplateContent, dockerComposeAbsolutePath) + _ <- EnvSust(envVars, nginxConfTemplateContent, nginxConfigurationFileAbsolutePath) + _ <- EnvSust(envVars, nginxPreSslConfTemplateContent, nginxPreSslConfigurationFileAbsolutePath) + _ <- EnvSust(envVarsPreBoot, dockerComposeTemplateContent, dockerComposeAbsolutePath) + _ <- EnvSust(envVars, sslCertificateGeneratorTemplateContent, sslCertificateGeneratorFileAbsolutePath) + _ <- ChmodX(sslCertificateGeneratorFileAbsolutePath) + yield () } private def generateConfigurationFilesPostSsl( inputVariables: FederationControllerInstallInputVariables, envVars: TrileFederationEnvVars - ): Unit = { + ): IO[Unit] = { val environment = inputVariables.environment val configurationFolderAbsolutePath = getConfigurationFolderAbsolutePath(envVars) val dockerComposeAbsolutePath = getDockerComposeAbsolutePath(configurationFolderAbsolutePath) @@ -168,7 +169,7 @@ object InstallControllerCommandHandler { EnvSust(envVarsPostSsl, dockerComposeTemplateContent, dockerComposeAbsolutePath) } - private def generateSslCertificate(envVars: TrileFederationEnvVars): Unit = { + private def generateSslCertificate(envVars: TrileFederationEnvVars): IO[Unit] = { val configurationFolderAbsolutePath = getConfigurationFolderAbsolutePath(envVars) val sslCertificateGeneratorFileAbsolutePath = getSslCertificateAbsolutePath(configurationFolderAbsolutePath) diff --git a/cli/src/main/scala/acab/devcon0/services/command/installer/InstallMemberCommandHandler.scala b/cli/src/main/scala/acab/devcon0/services/command/install/InstallMemberCommandHandler.scala similarity index 81% rename from cli/src/main/scala/acab/devcon0/services/command/installer/InstallMemberCommandHandler.scala rename to cli/src/main/scala/acab/devcon0/services/command/install/InstallMemberCommandHandler.scala index 81f8b2ec28f833fc00e10b648cb3599c922b84f1..e9e55edc90c45f6552ac155ffdf0f84b4b9d968f 100644 --- a/cli/src/main/scala/acab/devcon0/services/command/installer/InstallMemberCommandHandler.scala +++ b/cli/src/main/scala/acab/devcon0/services/command/install/InstallMemberCommandHandler.scala @@ -1,4 +1,4 @@ -package acab.devcon0.services.command.installer +package acab.devcon0.services.command.install import acab.devcon0.dtos.TrileCli.Component.* import acab.devcon0.dtos.TrileCli.EnvironmentVariables.* @@ -8,6 +8,7 @@ import acab.devcon0.dtos.{FederationMemberInstallInputVariables, TrileCli} import acab.devcon0.services.FederationMemberEnvironmentVariables import acab.devcon0.services.shell.{DockerComposeUp, EnvSust, MkdirP, SudoRm} import acab.devcon0.{files, services} +import cats.effect.IO import java.nio.charset.StandardCharsets import java.nio.file.{Files, Paths} @@ -17,7 +18,7 @@ object InstallMemberCommandHandler { def apply( inputVariables: FederationMemberInstallInputVariables, fedvarsAsMap: TrileFederationEnvVars - ): Unit = { + ): IO[Unit] = { val environmentVariables: TrileFederationEnvVars = FederationMemberEnvironmentVariables( inputVariables = inputVariables, fedvars = fedvarsAsMap @@ -32,18 +33,18 @@ object InstallMemberCommandHandler { val swarmKeyTemplateContent = files.Loader.get(SwarmKey, FederationMember, environment) val dockerComposeTemplateContent = files.Loader.get(DockerCompose, FederationMember, environment) - SudoRm(configurationFolderAbsolutePath) - MkdirP(configurationFolderAbsolutePath) - MkdirP(ipfsConfigurationFolderAbsolutePath) - MkdirP(ipfsClusterConfigurationFolderAbsolutePath) - MkdirP(federationMemberSharingFolder) - EnvSust(environmentVariables, swarmKeyTemplateContent, swarmKeyAbsolutePath) - EnvSust(environmentVariables, dockerComposeTemplateContent, dockerComposeAbsolutePath) - DockerComposeUp(dockerComposeAbsolutePath) - generateInstallationEnvVars(environmentVariables) + SudoRm(configurationFolderAbsolutePath) >> + MkdirP(configurationFolderAbsolutePath) >> + MkdirP(ipfsConfigurationFolderAbsolutePath) >> + MkdirP(ipfsClusterConfigurationFolderAbsolutePath) >> + MkdirP(federationMemberSharingFolder) >> + EnvSust(environmentVariables, swarmKeyTemplateContent, swarmKeyAbsolutePath) >> + EnvSust(environmentVariables, dockerComposeTemplateContent, dockerComposeAbsolutePath) >> + DockerComposeUp(dockerComposeAbsolutePath) >> + generateInstallationEnvVars(environmentVariables) } - private def generateInstallationEnvVars(envVars: TrileFederationEnvVars): Unit = { + private def generateInstallationEnvVars(envVars: TrileFederationEnvVars): IO[Unit] = IO { val configurationFolderAbsolutePath = getConfigurationFolderAbsolutePath(envVars) val installationEnvVarsAbsolutePath = s"$configurationFolderAbsolutePath/.installationEnvVars" diff --git a/cli/src/main/scala/acab/devcon0/services/command/update/UpdateControllerCommandHandler.scala b/cli/src/main/scala/acab/devcon0/services/command/update/UpdateControllerCommandHandler.scala index fa4ec147695611f07d17653ed9c66c552b507263..a3d61e03d112dee7111b4a9420849901e85a3ae5 100644 --- a/cli/src/main/scala/acab/devcon0/services/command/update/UpdateControllerCommandHandler.scala +++ b/cli/src/main/scala/acab/devcon0/services/command/update/UpdateControllerCommandHandler.scala @@ -1,4 +1,4 @@ -package acab.devcon0.services.command.installer +package acab.devcon0.services.command.update import acab.devcon0.dtos.* import acab.devcon0.dtos.TrileCli.Component.* @@ -8,11 +8,12 @@ import acab.devcon0.dtos.TrileCli.{Component, Defaults, Environment, File} import acab.devcon0.services.{EnvVarsFileLoader, PathSanitizer} import acab.devcon0.services.shell.{DockerComposeUp, EnvSust} import acab.devcon0.{files, services} +import cats.effect.IO import ujson.Value.Value object UpdateControllerCommandHandler { - def apply(inputVariables: FederationControllerUpdateInputVariables): Unit = { + def apply(inputVariables: FederationControllerUpdateInputVariables): IO[Unit] = { val userHomeAbsolutePath: String = PathSanitizer.expandHome(Defaults.userHomeAbsolutePath) val configurationPath: String = s"$userHomeAbsolutePath/.config/trile" val installationPath: String = s"$configurationPath/federations/${inputVariables.federationName}/controller" @@ -21,11 +22,11 @@ object UpdateControllerCommandHandler { val finalEnvVars: TrileFederationEnvVars = environmentVariables ++ fedVars val dockerComposeAbsolutePath = getDockerComposeAbsolutePath(installationPath) - updateConfigurationFiles(finalEnvVars, installationPath) - DockerComposeUp(dockerComposeAbsolutePath) + updateConfigurationFiles(finalEnvVars, installationPath) >> + DockerComposeUp(dockerComposeAbsolutePath) } - private def updateConfigurationFiles(envVars: TrileFederationEnvVars, installationPath: String): Unit = { + private def updateConfigurationFiles(envVars: TrileFederationEnvVars, installationPath: String): IO[Unit] = { val environment = Environment.valueOf(envVars(TRILE_ENVIRONMENT)) val dockerComposeAbsolutePath = getDockerComposeAbsolutePath(installationPath) val dockerComposeTemplateContent = files.Loader.get(DockerCompose, FederationController, environment) diff --git a/cli/src/main/scala/acab/devcon0/services/command/update/UpdateFederationCommandHandler.scala b/cli/src/main/scala/acab/devcon0/services/command/update/UpdateFederationCommandHandler.scala index 6dcb1307e2b177db5c1ad58b2ee34746af6421fe..1799faf0e55490ad2e8ff82f9d46215544bb9646 100644 --- a/cli/src/main/scala/acab/devcon0/services/command/update/UpdateFederationCommandHandler.scala +++ b/cli/src/main/scala/acab/devcon0/services/command/update/UpdateFederationCommandHandler.scala @@ -1,13 +1,14 @@ -package acab.devcon0.services.command.installer +package acab.devcon0.services.command.update import acab.devcon0.dtos.* -import acab.devcon0.dtosFederationMemberUpdateInputVariables.FederationMemberUpdateInputVariables import acab.devcon0.services.FederationMemberNicknameLister -import acab.devcon0.services.shell.PrintLn +import acab.devcon0.services.shell.{PrintLn, ShellExecutor} +import cats.Traverse.ops.toAllTraverseOps +import cats.effect.IO object UpdateFederationCommandHandler { - def apply(inputVariables: FederationUpdateInputVariables): Unit = { + def apply(inputVariables: FederationUpdateInputVariables): IO[Unit] = { val federationName: String = inputVariables.federationName val federationMemberNicknames: List[String] = FederationMemberNicknameLister(federationName) @@ -15,19 +16,24 @@ object UpdateFederationCommandHandler { federationName = federationName ) - PrintLn.cyan("Updating the controller.") - UpdateControllerCommandHandler(controllerInputVariables) - PrintLn.green("Controller updated") - - PrintLn.cyan(s"Updating the members ${federationMemberNicknames.mkString(" ")}") - federationMemberNicknames - .map(federationMemberNickname => - FederationMemberUpdateInputVariables( - federationName = federationName, - nickname = federationMemberNickname + for + _ <- IO(PrintLn.cyan("Updating the controller.")) + _ <- UpdateControllerCommandHandler(controllerInputVariables) + _ <- ShellExecutor.printProgressOk("Update controller") + _ <- IO(println()) + _ <- federationMemberNicknames + .map(federationMemberNickname => + FederationMemberUpdateInputVariables( + federationName = federationName, + nickname = federationMemberNickname + ) ) - ) - .foreach(UpdateMemberCommandHandler(_)) - PrintLn.green("Members updated") + .traverse(inputVariables => { + IO(PrintLn.cyan(s"Updating member ${inputVariables.nickname}")) >> + UpdateMemberCommandHandler(inputVariables) >> + ShellExecutor.printProgressOk(s"Update member ${inputVariables.nickname}") >> + IO(println()) + }) + yield () } } diff --git a/cli/src/main/scala/acab/devcon0/services/command/update/UpdateMemberCommandHandler.scala b/cli/src/main/scala/acab/devcon0/services/command/update/UpdateMemberCommandHandler.scala index 9179d9a492d44188712c957eb8aef63ac1de6068..e4dedb4579b0308931e5415add6457721985dfda 100644 --- a/cli/src/main/scala/acab/devcon0/services/command/update/UpdateMemberCommandHandler.scala +++ b/cli/src/main/scala/acab/devcon0/services/command/update/UpdateMemberCommandHandler.scala @@ -1,19 +1,19 @@ -package acab.devcon0.services.command.installer +package acab.devcon0.services.command.update import acab.devcon0.dtos.* import acab.devcon0.dtos.TrileCli.Component.* import acab.devcon0.dtos.TrileCli.EnvironmentVariables.* import acab.devcon0.dtos.TrileCli.File.* import acab.devcon0.dtos.TrileCli.{Component, Defaults, Environment, File} -import acab.devcon0.dtosFederationMemberUpdateInputVariables.FederationMemberUpdateInputVariables import acab.devcon0.services.{EnvVarsFileLoader, PathSanitizer} import acab.devcon0.services.shell.{DockerComposeUp, EnvSust} import acab.devcon0.{files, services} +import cats.effect.IO import ujson.Value.Value object UpdateMemberCommandHandler { - def apply(inputVariables: FederationMemberUpdateInputVariables): Unit = { + def apply(inputVariables: FederationMemberUpdateInputVariables): IO[Unit] = { val userHomeAbsolutePath: String = PathSanitizer.expandHome(Defaults.userHomeAbsolutePath) val configurationPath: String = s"$userHomeAbsolutePath/.config/trile" val federationName: String = inputVariables.federationName @@ -22,11 +22,11 @@ object UpdateMemberCommandHandler { val environmentVariables: TrileFederationEnvVars = EnvVarsFileLoader(installationPath + "/.installationEnvVars") val dockerComposeAbsolutePath: String = getDockerComposeAbsolutePath(installationPath) - updateConfigurationFiles(environmentVariables, installationPath) - DockerComposeUp(dockerComposeAbsolutePath) + updateConfigurationFiles(environmentVariables, installationPath) >> + DockerComposeUp(dockerComposeAbsolutePath) } - private def updateConfigurationFiles(envVars: TrileFederationEnvVars, installationPath: String): Unit = { + private def updateConfigurationFiles(envVars: TrileFederationEnvVars, installationPath: String): IO[Unit] = { val environment = Environment.valueOf(envVars(TRILE_ENVIRONMENT)) val dockerComposeAbsolutePath = getDockerComposeAbsolutePath(installationPath) val dockerComposeTemplateContent = files.Loader.get(DockerCompose, FederationMember, environment) diff --git a/cli/src/main/scala/acab/devcon0/services/shell/Bash.scala b/cli/src/main/scala/acab/devcon0/services/shell/Bash.scala index 50dabd6232fd90c9ec0d5a4f13eefbb39fd125b5..96624af619bf2456a0b8073ee222e465855486b2 100644 --- a/cli/src/main/scala/acab/devcon0/services/shell/Bash.scala +++ b/cli/src/main/scala/acab/devcon0/services/shell/Bash.scala @@ -1,15 +1,16 @@ package acab.devcon0.services.shell -import scala.scalanative.libc.stdlib -import scala.scalanative.unsafe.Zone +import cats.effect.IO object Bash { - def apply(commandInput: String): Unit = { - Zone { implicit z => - val commandStr = s"bash -c \"$commandInput\"" - println(s"> $commandStr") - val command = scala.scalanative.unsafe.toCString(commandStr) - stdlib.system(command) - } + private val cmdLabel: String = "Executing shell command" + + def apply(commandInput: String): IO[Unit] = { + val cmd: String = getCmd(commandInput) + ShellExecutor.withoutProgress(cmd, s"$cmdLabel: $commandInput", commandInput.length) + } + + private def getCmd(commandInput: String): String = { + s"bash -c \"$commandInput\"" } } diff --git a/cli/src/main/scala/acab/devcon0/services/shell/ChmodX.scala b/cli/src/main/scala/acab/devcon0/services/shell/ChmodX.scala index 907203d726b598618c8de339c509feacf72ff8df..8dc1bd5de9fae77d456393bf26adcc2164cb6cdd 100644 --- a/cli/src/main/scala/acab/devcon0/services/shell/ChmodX.scala +++ b/cli/src/main/scala/acab/devcon0/services/shell/ChmodX.scala @@ -1,15 +1,18 @@ package acab.devcon0.services.shell -import scala.scalanative.libc.stdlib -import scala.scalanative.unsafe.Zone +import cats.effect.IO object ChmodX { - def apply(path: String): Unit = { - Zone { implicit z => - val commandStr = s"chmod +x $path" - println(s"> $commandStr") - val command = scala.scalanative.unsafe.toCString(commandStr) - stdlib.system(command) - } + + private val cmdLabel: String = "Ensuring permissions" + + def apply(path: String): IO[Unit] = { + val cmd: String = getCmd(path) + val label = s"$cmdLabel: ${path.split('/').lastOption.getOrElse(path)}" + ShellExecutor.withoutProgress(cmd, label, label.length) + } + + private def getCmd(path: String): String = { + s"chmod +x $path" } } diff --git a/cli/src/main/scala/acab/devcon0/services/shell/DockerComposeUp.scala b/cli/src/main/scala/acab/devcon0/services/shell/DockerComposeUp.scala index b5f07ed9d285b08cbda1201b618d3aabce29888a..1beaa407625dd26115030bc95e548f784046ac04 100644 --- a/cli/src/main/scala/acab/devcon0/services/shell/DockerComposeUp.scala +++ b/cli/src/main/scala/acab/devcon0/services/shell/DockerComposeUp.scala @@ -1,15 +1,19 @@ package acab.devcon0.services.shell -import scala.scalanative.libc.stdlib -import scala.scalanative.unsafe.{Zone, toCString} +import cats.effect.IO + +import scala.scalanative.unsafe object DockerComposeUp { - def apply(dockerComposeAbsolutePath: String): Unit = { - Zone { implicit z => - val pull: String = s"docker compose -f $dockerComposeAbsolutePath pull" - val upForcedAndDetached: String = s"docker compose -f $dockerComposeAbsolutePath up --force-recreate -d" - stdlib.system(toCString(pull)) - stdlib.system(toCString(upForcedAndDetached)) - } + + private val cmdLabel = "Booting containers" + + def apply(dockerComposeAbsolutePath: String): IO[Unit] = { + val cmd: String = getCmd(dockerComposeAbsolutePath) + ShellExecutor.withProgress(cmd, cmdLabel) + } + + private def getCmd(dockerComposeAbsolutePath: String): String = { + s"docker compose --progress=quiet -f $dockerComposeAbsolutePath up --pull always --quiet-pull --force-recreate --detach --wait" } } diff --git a/cli/src/main/scala/acab/devcon0/services/shell/EnvSust.scala b/cli/src/main/scala/acab/devcon0/services/shell/EnvSust.scala index dee5cd8e1f74f6cb53ecaee38afb5207293b9863..cc92b9e48e6b1aabd19ba47731bd0d540d48eb15 100644 --- a/cli/src/main/scala/acab/devcon0/services/shell/EnvSust.scala +++ b/cli/src/main/scala/acab/devcon0/services/shell/EnvSust.scala @@ -1,16 +1,30 @@ package acab.devcon0.services.shell +import cats.effect.IO +import cats.implicits.catsSyntaxMonadError + import java.nio.charset.StandardCharsets import java.nio.file.{Files, Paths} -import scala.io.Source object EnvSust { def apply( envVarsMap: Map[String, String], templateContent: String, absoluteOutputPath: String + ): IO[Unit] = { + val cmdLabel = s"File generation: ${absoluteOutputPath.split('/').lastOption.getOrElse(absoluteOutputPath)}" + IO(applyInner(envVarsMap, templateContent, absoluteOutputPath, true)) + .attemptTap[Unit](either => ShellExecutor.attemptTapPrintProgressFinal(cmdLabel, either)) + } + + private def applyInner( + envVarsMap: Map[String, String], + templateContent: String, + absoluteOutputPath: String, + quiet: Boolean ): Unit = { - println(s"> envsust < .. > $absoluteOutputPath ") + if !quiet then println(s"> envsust < .. > $absoluteOutputPath ") + val replacedContent = envVarsMap.foldLeft(templateContent) { (content, envVar) => content.replace( s"$${${envVar._1}}", diff --git a/cli/src/main/scala/acab/devcon0/services/shell/IpfsGenerateSwarmKey.scala b/cli/src/main/scala/acab/devcon0/services/shell/IpfsGenerateSwarmKey.scala index 7bce267110f434b1b8d89c37c3b5878a5b3a8701..de5ee916ba72c407aedf2f755ac121ca920c7bf6 100644 --- a/cli/src/main/scala/acab/devcon0/services/shell/IpfsGenerateSwarmKey.scala +++ b/cli/src/main/scala/acab/devcon0/services/shell/IpfsGenerateSwarmKey.scala @@ -1,15 +1,21 @@ package acab.devcon0.services.shell +import cats.effect.IO + import scala.scalanative.libc.stdlib import scala.scalanative.unsafe.{Zone, toCString} object IpfsGenerateSwarmKey { - def apply(swarmKeyAbsolutePath: String): Unit = { - Zone { implicit z => - val commandStr: String = - s"echo \"/key/swarm/psk/1.0.0/\n/base16/\n$$(hexdump -n 32 -e '16/1 \"%02x\"' /dev/urandom)\" > $swarmKeyAbsolutePath" - println(s"> $commandStr") - stdlib.system(toCString(commandStr)) - } + + private val cmdLabel: String = "Creating IPFS swarm key" + + def apply(swarmKeyAbsolutePath: String): IO[Unit] = { + val cmd: String = getCmd(swarmKeyAbsolutePath) + val label = s"$cmdLabel: ${swarmKeyAbsolutePath.split('/').lastOption.getOrElse(swarmKeyAbsolutePath)}" + ShellExecutor.withoutProgress(cmd, label, label.length) + } + + private def getCmd(swarmKeyAbsolutePath: String): String = { + s"echo \"/key/swarm/psk/1.0.0/\n/base16/\n$$(hexdump -n 32 -e '16/1 \"%02x\"' /dev/urandom)\" > $swarmKeyAbsolutePath" } } diff --git a/cli/src/main/scala/acab/devcon0/services/shell/MkdirP.scala b/cli/src/main/scala/acab/devcon0/services/shell/MkdirP.scala index d299f4c3cb59c4628b7340fcbbbed58105150576..1ac1bdc8a0ee3db1e93ebfdc96371f243913ffcd 100644 --- a/cli/src/main/scala/acab/devcon0/services/shell/MkdirP.scala +++ b/cli/src/main/scala/acab/devcon0/services/shell/MkdirP.scala @@ -1,15 +1,18 @@ package acab.devcon0.services.shell -import scala.scalanative.libc.stdlib -import scala.scalanative.unsafe.Zone +import cats.effect.IO object MkdirP { - def apply(folderPath: String): Unit = { - Zone { implicit z => - val commandStr = s"mkdir -p $folderPath" - println(s"> $commandStr") - val command = scala.scalanative.unsafe.toCString(commandStr) - stdlib.system(command) - } + + private val cmdLabel: String = "Ensuring folder exists" + + def apply(folderPath: String): IO[Unit] = { + val cmd: String = getCmd(folderPath) + val label = s"$cmdLabel: ${folderPath.split('/').lastOption.getOrElse(folderPath)}" + ShellExecutor.withProgress(cmd, label) + } + + private def getCmd(folderPath: String): String = { + s"mkdir -p $folderPath" } } diff --git a/cli/src/main/scala/acab/devcon0/services/shell/ShellExecutor.scala b/cli/src/main/scala/acab/devcon0/services/shell/ShellExecutor.scala new file mode 100644 index 0000000000000000000000000000000000000000..fb0701aa44cb78835300aa07258e9df13d0f1a31 --- /dev/null +++ b/cli/src/main/scala/acab/devcon0/services/shell/ShellExecutor.scala @@ -0,0 +1,89 @@ +package acab.devcon0.services.shell + +import cats.effect.IO + +import scala.scalanative.libc.stdlib +import scala.scalanative.unsafe +import scala.scalanative.unsafe.{CInt, Zone, toCString} + +object ShellExecutor { + + def withoutProgress(cmd: String, label: String, wipingLineLength: Int): IO[Unit] = { + getCmdIO(cmd) + .flatMap(cInt => { + if cInt != 0 then replacePrintProgressError(label) >> IO.raiseError(NonZeroExitCode()) + else replacePrintProgressOk(label, Some(wipingLineLength)) + }) + } + + def withProgress(cmd: String, label: String): IO[Unit] = { + getCmdIO(appendProgress(cmd, label)) + .flatMap(cInt => { + if cInt != 0 then replacePrintProgressError(label) >> IO.raiseError(NonZeroExitCode()) + else replacePrintProgressOk(label) + }) + } + + def filterNonZeroExitCode(cInt: CInt): IO[Unit] = { + if cInt != 0 then IO.raiseError(NonZeroExitCode()) + else IO.unit + } + + def attemptTapPrintProgressFinal(label: String, either: Either[Throwable, ?]): IO[Unit] = { + either + .fold(_ => printProgressError(label), _ => printProgressOk(label)) + } + + def printProgressOk(label: String): IO[Unit] = { + getCmdIO(s"printf \"\\033[1;32m[OK]\\033[0;32m $label \\033[0m\"") + .flatMap(_ => IO(println())) + } + + def printProgressError(label: String): IO[Unit] = { + getCmdIO(s"printf \"\\033[1;31m[Error]\\033[0;31m $label \\033[0m\"") + .flatMap(_ => IO(println())) + } + + private def replacePrintProgressOk(label: String, wipingLineLength: Option[Int] = None): IO[Unit] = { + val whiteChars: String = " " * wipingLineLength.getOrElse(label.length) + getCmdIO(s"printf \"\\r\\033[1;32m[OK]\\033[0;32m $label $whiteChars\\033[0m\"") + .flatMap(_ => IO(println())) + } + + private def replacePrintProgressError(label: String): IO[Unit] = { + getCmdIO(s"printf \"\\r\\033[1;31m[Error]\\033[0;31m $label \\033[0m\"") + .flatMap(_ => IO(println())) + } + + private def appendProgress(cmd: String, label: String): String = { + s"""$cmd & +pid=$$! # Process Id of the previous running command + +Off='\\033[0m' +BBlue='\\033[1;34m' + +i=0 +while kill -0 $$pid 2>/dev/null +do + for s in / - \\\\ \\|; do + printf \"\\r \\033[1;34m [$$s] \\033[0m $label\" + sleep .1 + done + i=$$((i+1)) +done +wait $$pid +exit $$? +""".stripMargin + } + + private def getCmdIO(cmd: String): IO[CInt] = { + IO { + Zone { implicit z => + stdlib.system(toCString(cmd)) + } + } + } + +} + +final case class NonZeroExitCode() extends Throwable diff --git a/cli/src/main/scala/acab/devcon0/services/shell/SudoRm.scala b/cli/src/main/scala/acab/devcon0/services/shell/SudoRm.scala index 6d3e040c631f8d0c19f70b4e8668f967749c91bf..767bfeb7b7677e01a65294354742bcf5e685368d 100644 --- a/cli/src/main/scala/acab/devcon0/services/shell/SudoRm.scala +++ b/cli/src/main/scala/acab/devcon0/services/shell/SudoRm.scala @@ -1,15 +1,23 @@ package acab.devcon0.services.shell -import scala.scalanative.libc.stdlib -import scala.scalanative.unsafe.Zone +import cats.effect.IO object SudoRm { - def apply(folderPath: String): Unit = { - Zone { implicit z => - val commandStr = s"sudo rm -rf $folderPath" - println(s"> $commandStr") - val command = scala.scalanative.unsafe.toCString(commandStr) - stdlib.system(command) - } + + private val cmdLabel: String = "Deleting folder" + + def apply(folderPath: String): IO[Unit] = { + val message = s"Up to delete $folderPath folder as sudo" + val cmd: String = getCmd(folderPath, message) + ShellExecutor.withoutProgress(cmd, s"$cmdLabel: $folderPath", message.length) + } + + private def getCmd(folderPath: String, message: String): String = { + s""" +if [ -d "$folderPath" ]; then + printf \"$message\\n\" + sudo rm -rf "$folderPath" +fi + """ } }