diff --git a/cli/src/main/scala/acab/devcon0/dtos/FederationMemberInstallInputVariables.scala b/cli/src/main/scala/acab/devcon0/dtos/FederationMemberInstallInputVariables.scala index 76a82089876efcac1178d7673bdad63a6a96b9dd..de9541b3feb980cb521ad76581cb1859230ef7dc 100644 --- a/cli/src/main/scala/acab/devcon0/dtos/FederationMemberInstallInputVariables.scala +++ b/cli/src/main/scala/acab/devcon0/dtos/FederationMemberInstallInputVariables.scala @@ -1,11 +1,12 @@ package acab.devcon0.dtos import acab.devcon0.dtos.TrileCli.Environment +import acab.devcon0.dtos.TrileCli.EnvironmentVariables.TrileFederationEnvVars case class FederationMemberInstallInputVariables( environment: Environment, nickname: String, - fedvarsAbsolutePath: String, + fedvarsMap: TrileFederationEnvVars, sharedDiskSpaceGB: Int, sharingFolder: String ) diff --git a/cli/src/main/scala/acab/devcon0/dtos/TrileCli.scala b/cli/src/main/scala/acab/devcon0/dtos/TrileCli.scala index cac2ee99569573a078ea9e353e80f7d68db5c527..ccc19b16c2400e20ab152bcbc3e5f74df1221a15 100644 --- a/cli/src/main/scala/acab/devcon0/dtos/TrileCli.scala +++ b/cli/src/main/scala/acab/devcon0/dtos/TrileCli.scala @@ -35,6 +35,7 @@ object TrileCli { val TFC_CERTBOT_SERVICE_NAME = "TRILE_FEDERATION_CONTROLLER_CERTBOT_SERVICE_NAME" val TFC_CERTBOT_STAGING = "TRILE_FEDERATION_CONTROLLER_CERTBOT_STAGING" val TFC_CONFIGURATION_FOLDER = "TRILE_FEDERATION_CONTROLLER_CONFIGURATION_FOLDER" + val TFC_FEDVARS_ABSOLUTE_PATH = "TRILE_FEDERATION_CONTROLLER_FEDVARS_ABSOLUTE_PATH" val TFC_IPFS_ADDRESS = "TRILE_FEDERATION_CONTROLLER_IPFS_ADDRESS" val TFC_IPFS_CLUSTER_ADDRESS = "TRILE_FEDERATION_CONTROLLER_IPFS_CLUSTER_ADDRESS" val TFC_IPFS_CLUSTER_PEER_ID = "TRILE_FEDERATION_CONTROLLER_IPFS_CLUSTER_PEER_ID" @@ -66,15 +67,14 @@ object TrileCli { private val cliDirectoryAbsolutePath: String = System.getProperty("user.dir") - val userHomeAbsolutePath: String = System.getProperty("user.home") - val ipfsSwarmPort: String = "4001" - val ipfsClusterSwarmPort: String = "9096" - val sharedDiskSpaceGb: Int = 10 - val environment = "production" - val fedvarsAbsolutePath: String = s"$cliDirectoryAbsolutePath/.fedvars" - val nickname: String = userHomeAbsolutePath.split("/").last - val federationMemberBackendPrivatePort: Int = 9999 - val trileConfigurationAbsolutePath = s"$userHomeAbsolutePath/.config/trile" - val ipfsClusterReplicaFactorMax = 5 + val userHomeAbsolutePath: String = System.getProperty("user.home") + val ipfsSwarmPort: String = "4001" + val ipfsClusterSwarmPort: String = "9096" + val sharedDiskSpaceGb: Int = 10 + val environment = "production" + val nickname: String = userHomeAbsolutePath.split("/").last + val federationMemberBackendPrivatePort: Int = 9999 + val trileConfigurationAbsolutePath = s"$userHomeAbsolutePath/.config/trile" + val ipfsClusterReplicaFactorMax = 5 } } diff --git a/cli/src/main/scala/acab/devcon0/files/templates/dev/federationcontroller/DockerCompose.scala b/cli/src/main/scala/acab/devcon0/files/templates/dev/federationcontroller/DockerCompose.scala index 473ae0836097e289fda120b096692d19353c4b87..6f373e37f138e0d11399333151dea6b89ec168ef 100644 --- a/cli/src/main/scala/acab/devcon0/files/templates/dev/federationcontroller/DockerCompose.scala +++ b/cli/src/main/scala/acab/devcon0/files/templates/dev/federationcontroller/DockerCompose.scala @@ -50,9 +50,11 @@ services: TRILE_FEDERATION_CONTROLLER_IPFS_API_URL: http://${TRILE_IPFS_DOCKER_CONTAINER_NAME}:5001/api/v0/ TRILE_FEDERATION_CONTROLLER_IPFS_CLUSTER_API_URL: http://${TRILE_IPFS_CLUSTER_DOCKER_CONTAINER_NAME}:9094/ TRILE_FEDERATION_CONTROLLER_IPFS_SWARM_KEY_VALUE: ${TRILE_FEDERATION_IPFS_SWARM_KEY_VALUE} + TRILE_FEDERATION_CONTROLLER_FEDVARS_ABSOLUTE_PATH: /data/.fedvars TRILE_FEDERATION_CONTROLLER_P2P_PRIVATE_KEY: ${TRILE_FEDERATION_CONTROLLER_P2P_PRIVATE_KEY} TRILE_FEDERATION_CONTROLLER_REDIS_HOST: trile-dev-federation-controller-redis-stack volumes: + - ${TRILE_FEDERATION_CONTROLLER_FEDVARS_ABSOLUTE_PATH}:/data/.fedvars - /var/run/docker.sock:/var/run/docker.sock trile_dev_federation_controller_ipfs_cluster: 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 0b59483c4e81c5008378adf4339a5c1710a45643..b526b9fffd1f876359ab8e610f69906221a61cb1 100644 --- a/cli/src/main/scala/acab/devcon0/program/commands/InstallFederationMemberCommand.scala +++ b/cli/src/main/scala/acab/devcon0/program/commands/InstallFederationMemberCommand.scala @@ -6,7 +6,7 @@ import acab.devcon0.dtos.TrileCli.Environment import acab.devcon0.dtos.TrileCli.Environment.* import acab.devcon0.dtos.TrileCli.EnvironmentVariables.* import acab.devcon0.program.opts.{TrileOptsFederation, TrileOptsFederationMember} -import acab.devcon0.services.EnvVarsFileLoader +import acab.devcon0.services.{EnvVarsFileLoader, FederationControllerService} import acab.devcon0.services.command.install.InstallMemberCommandHandler import acab.devcon0.services.shell.{Clear, ConfirmAndRun, Greeter, PrintLn} import cats.effect.IO @@ -27,7 +27,7 @@ object InstallFederationMemberCommand { TrileOptsFederationMember.nicknameOpt, TrileOptsFederationMember.sharedDiskSpaceGBMandatoryOpt, TrileOptsFederationMember.sharingFolderMandatoryOpt, - TrileOptsFederation.fedvarsAbsolutePathOpt, + TrileOptsFederation.federationUrl, TrileOptsFederation.assumeYesFlag ).mapN { ( @@ -35,30 +35,35 @@ object InstallFederationMemberCommand { nickname, sharedDiskSpaceGBMandatory, sharingFolderMandatory, - fedvarsAbsolutePath, + federationUrl, assumeYes ) => - val fedvarsMap: TrileFederationEnvVars = - EnvVarsFileLoader(fedvarsAbsolutePath) - - Greeter(action = s"join the '${fedvarsMap(TRILE_FEDERATION_NAME)}'") - - val sharedDiskSpaceGB: Int = loadOrReadSharedDiskSpaceGB(sharedDiskSpaceGBMandatory) - val sharingFolder = loadOrReadSharingFolder(sharingFolderMandatory) - val federationMemberInstallInputVariables: FederationMemberInstallInputVariables = - FederationMemberInstallInputVariables( - environment, - nickname, - fedvarsAbsolutePath, - sharedDiskSpaceGB, - sharingFolder - ) - printInputVariables(federationMemberInstallInputVariables) - - ConfirmAndRun( - skipConfirmation = assumeYes, - action = () => triggerInstallation(fedvarsMap, federationMemberInstallInputVariables) - ) + { + for fedvarsMap <- FederationControllerService.getFedvars(environment, federationUrl) + yield { + Greeter(action = s"join the '${fedvarsMap(TRILE_FEDERATION_NAME)}'") + + val sharedDiskSpaceGB: Int = loadOrReadSharedDiskSpaceGB(sharedDiskSpaceGBMandatory) + val sharingFolder = loadOrReadSharingFolder(sharingFolderMandatory) + val federationMemberInstallInputVariables: FederationMemberInstallInputVariables = + FederationMemberInstallInputVariables( + environment, + nickname, + fedvarsMap, + sharedDiskSpaceGB, + sharingFolder + ) + printInputVariables(federationMemberInstallInputVariables) + + ConfirmAndRun( + skipConfirmation = assumeYes, + action = () => triggerInstallation(fedvarsMap, federationMemberInstallInputVariables) + ) + } + } + .attemptTap(_.fold(throwable => IO(println(throwable)), result => IO(println(result)))) + .unsafeToFuture()(TrileCommand.trileRuntime) + .wait() } } @@ -81,7 +86,7 @@ object InstallFederationMemberCommand { println(s"- Shared disk space (GBs): ${inputVariables.sharedDiskSpaceGB}") println(s"- Shared folder: ${inputVariables.sharingFolder}") println(s"- Backend port: ${inputVariables.sharingFolder}") - println(s"- .fedvars location: ${inputVariables.fedvarsAbsolutePath}") + println(s"- .fedvars content: ${inputVariables.fedvarsMap}") println() } diff --git a/cli/src/main/scala/acab/devcon0/program/opts/TrileOptsFederation.scala b/cli/src/main/scala/acab/devcon0/program/opts/TrileOptsFederation.scala index 5ac1abaea4586633385c3b8f9d6c7c3994da9fe6..b1f00ba75dcabbf4669dfb8110262addd002dd08 100644 --- a/cli/src/main/scala/acab/devcon0/program/opts/TrileOptsFederation.scala +++ b/cli/src/main/scala/acab/devcon0/program/opts/TrileOptsFederation.scala @@ -16,12 +16,11 @@ object TrileOptsFederation { val federationNameOpt: Opts[String] = Opts .option[String](long = "federation-name", short = "f", help = s"The name you want to use in the federation") - val fedvarsAbsolutePathOpt: Opts[String] = Opts + val federationUrl: Opts[String] = Opts .option[String]( - "fedvars-file", - help = s"Absolute path for the federation variables files (default=${Defaults.fedvarsAbsolutePath})" + "federation-url", + help = s"URL of the federation. For instance: demo.trile.link" ) - .withDefault(Defaults.fedvarsAbsolutePath) .map(PathSanitizer.expandHome) val assumeYesFlag: Opts[Boolean] = Opts diff --git a/cli/src/main/scala/acab/devcon0/services/FederationControllerEnvironmentVariables.scala b/cli/src/main/scala/acab/devcon0/services/FederationControllerEnvironmentVariables.scala index 14ec7a398ff417eccb575664ac099e4c10067ff1..05ae801e1caea0c02097f426b3a8362a8e6916b0 100644 --- a/cli/src/main/scala/acab/devcon0/services/FederationControllerEnvironmentVariables.scala +++ b/cli/src/main/scala/acab/devcon0/services/FederationControllerEnvironmentVariables.scala @@ -29,10 +29,9 @@ object FederationControllerEnvironmentVariables { val backendServiceName: String = backendContainerName.replace('-', '_') val ipfsSwarmPort: String = Defaults.ipfsSwarmPort val ipfsClusterSwarmPort: String = Defaults.ipfsClusterSwarmPort - val certbotDirectory = inputVariables.certbotFolderAbsolutePathOptional - .getOrElse(s"$controllerConfigurationFolder/certbot") - val sharedDiskSpaceGb: Int = inputVariables.sharedDiskSpaceGb - .getOrElse(Defaults.sharedDiskSpaceGb) + val sharedDiskSpaceGb: Int = inputVariables.sharedDiskSpaceGb.getOrElse(Defaults.sharedDiskSpaceGb) + val certbotDirectory = + inputVariables.certbotFolderAbsolutePathOptional.getOrElse(s"$controllerConfigurationFolder/certbot") Map( DOLLAR -> "$", HOST_UID -> UserId().toString, @@ -52,6 +51,7 @@ object FederationControllerEnvironmentVariables { TFC_CERTBOT_SERVICE_NAME -> certbotServiceName, TFC_CERTBOT_STAGING -> inputVariables.stagingSSL, TFC_CONFIGURATION_FOLDER -> controllerConfigurationFolder, + TFC_FEDVARS_ABSOLUTE_PATH -> s"$controllerConfigurationFolder/.fedvars", TFC_IPFS_CLUSTER_ADDRESS -> getIpfsClusterAddress(controllerNetworkAddress, environment, ipfsClusterSwarmPort), TFC_IPFS_CLUSTER_REPLICA_FACTOR_MAX -> inputVariables.ipfsClusterReplicaFactorMax.toString, TFC_IPFS_MAX_DISK_SPACE_GB -> sharedDiskSpaceGb.toString, diff --git a/cli/src/main/scala/acab/devcon0/services/FederationControllerService.scala b/cli/src/main/scala/acab/devcon0/services/FederationControllerService.scala index 946467aa561443d8adf375ee8b35c6a06c304d52..47e9f12c42fa7cda0e78648b088b75c40633a7c7 100644 --- a/cli/src/main/scala/acab/devcon0/services/FederationControllerService.scala +++ b/cli/src/main/scala/acab/devcon0/services/FederationControllerService.scala @@ -2,13 +2,14 @@ package acab.devcon0.services import acab.devcon0.dtos.TrileCli import acab.devcon0.dtos.TrileCli.Environment +import acab.devcon0.dtos.TrileCli.EnvironmentVariables.TrileFederationEnvVars import acab.devcon0.dtos.aliases.P2pPeerId import acab.devcon0.services.shell.PrintLn import cats.effect.IO import cats.implicits.catsSyntaxMonadError import sttp.client4.curl.CurlBackend -import sttp.client4.{SyncBackend, UriContext, quickRequest} -import sttp.model.{StatusCode, Uri} +import sttp.client4.{Response, SyncBackend, UriContext, quickRequest} +import sttp.model.{HeaderNames, StatusCode, Uri} import java.util.concurrent.TimeUnit import scala.concurrent.duration.Duration @@ -16,13 +17,33 @@ import scala.concurrent.duration.Duration object FederationControllerService { private val backend: SyncBackend = CurlBackend() - def getPeerId(environment: Environment, networkAddress: P2pPeerId): IO[P2pPeerId] = { - val uri: Uri = getUri(environment, networkAddress) + def getFedvars(environment: Environment, networkAddress: String): IO[TrileFederationEnvVars] = { + val uri: Uri = getFedvarsUri(environment, networkAddress) + triggerFedvarsRequest(uri) + .map( + _.split("\\\\n") + .map { line => + val Array(key, value) = line.split("=", 2) + key -> value + } + .toMap + ) + .attemptTap(_.fold(throwable => IO(println(throwable.getMessage + uri)), result => IO(println(result)))) + } + + def getPeerId(environment: Environment, networkAddress: String): IO[P2pPeerId] = { + val uri: Uri = getIdUri(environment, networkAddress) getP2pPeerIdInner(uri, 300) .attemptTap(PrintLn.subStepAttemptTap("Fetching P2P peer id", _)) } - private def getUri(environment: Environment, networkAddress: P2pPeerId): Uri = { + private def getFedvarsUri(environment: Environment, networkAddress: P2pPeerId): Uri = { + environment match + case TrileCli.Environment.Dev => uri"http://$networkAddress:8078/api/federation/.fedvars" + case TrileCli.Environment.Production => uri"https://api.$networkAddress/api/federation/.fedvars" + } + + private def getIdUri(environment: Environment, networkAddress: P2pPeerId): Uri = { environment match case TrileCli.Environment.Dev => uri"http://$networkAddress:8078/api/p2p/id" case TrileCli.Environment.Production => uri"https://api.$networkAddress/api/p2p/id" @@ -36,13 +57,25 @@ object FederationControllerService { } } + private def triggerFedvarsRequest(uri: Uri): IO[String] = { + triggerRequest(IO(quickRequest.get(uri).header(HeaderNames.Authorization, "Bearer TOKEN").send(backend))) + } + private def triggerRequest(uri: Uri): IO[String] = { - IO(quickRequest.get(uri).send(backend)) + triggerRequest(IO(quickRequest.get(uri).send(backend))) + } + + private def triggerRequest(value: IO[Response[String]]): IO[String] = { + value .flatMap(response => { if response.code.equals(StatusCode.Ok) then IO(response) - else IO.raiseError(new IllegalArgumentException()) + else { + println(response) + IO.raiseError(new IllegalArgumentException()) + } }) .map(_.body) .map(_.stripPrefix("\"").stripSuffix("\"")) + } } diff --git a/cli/src/main/scala/acab/devcon0/services/command/install/InstallControllerCommandHandler.scala b/cli/src/main/scala/acab/devcon0/services/command/install/InstallControllerCommandHandler.scala index 1b0e0f995d30584a9489a735bb6373f1e27fb93c..7731e1a7e6d740bc54ae79805ac3a276eeefb9ac 100644 --- a/cli/src/main/scala/acab/devcon0/services/command/install/InstallControllerCommandHandler.scala +++ b/cli/src/main/scala/acab/devcon0/services/command/install/InstallControllerCommandHandler.scala @@ -42,8 +42,7 @@ object InstallControllerCommandHandler { val environment: Environment = inputVariables.environment for peerId <- FederationControllerService.getPeerId(environment, networkAddress) yield { - val configurationFolderAbsolutePath = getConfigurationFolderAbsolutePath(environmentVariablesMap) - val fedvarsAbsolutePath = s"$configurationFolderAbsolutePath/.fedvars" + val fedvarsAbsolutePath = environmentVariablesMap(TFC_FEDVARS_ABSOLUTE_PATH) val map: TrileFederationEnvVars = Map( TRILE_FEDERATION_IPFS_SWARM_KEY_VALUE -> getIpfsSwarmKeyValue(environmentVariablesMap), TFC_NETWORK_ADDRESS -> networkAddress, @@ -130,6 +129,7 @@ object InstallControllerCommandHandler { val nginxPreSslConfigurationFileAbsolutePath = getNginxPreSslConfigurationFileAbsolutePath(nginxFolderAbsolutePath) val swarmKeyAbsolutePath = ipfsConfigurationFolderAbsolutePath + "/swarm.key" val dockerComposeAbsolutePath = getDockerComposeAbsolutePath(configFolderAbsolutePath) + val fedvarsAbsolutePath = geFedvarsAbsolutePath(envVars) val dockerComposeTemplateContent = files.Loader.get(DockerCompose, FederationController, environment) val nginxConfTemplateContent = files.Loader.get(NginxConf, FederationController, environment) @@ -139,6 +139,7 @@ object InstallControllerCommandHandler { for _ <- SudoRm(configFolderAbsolutePath) _ <- MkdirP(configFolderAbsolutePath) + _ <- Touch(fedvarsAbsolutePath) _ <- MkdirP(certbotConfigurationFolderAbsolutePath) _ <- MkdirP(nginxFolderAbsolutePath) _ <- MkdirP(ipfsConfigurationFolderAbsolutePath) @@ -200,6 +201,10 @@ object InstallControllerCommandHandler { nginxConfigurationFolderAbsolutePath + "/nginx-pre-ssl.conf" } + private def geFedvarsAbsolutePath(envVars: TrileFederationEnvVars): String = { + envVars(TFC_FEDVARS_ABSOLUTE_PATH) + } + private def getConfigurationFolderAbsolutePath(envVars: TrileFederationEnvVars): String = { envVars(TFC_CONFIGURATION_FOLDER) } diff --git a/cli/src/main/scala/acab/devcon0/services/shell/Touch.scala b/cli/src/main/scala/acab/devcon0/services/shell/Touch.scala new file mode 100644 index 0000000000000000000000000000000000000000..3b7d5cd84853e98cf881e3eb39e0226f9e6cd028 --- /dev/null +++ b/cli/src/main/scala/acab/devcon0/services/shell/Touch.scala @@ -0,0 +1,18 @@ +package acab.devcon0.services.shell + +import cats.effect.IO + +object Touch { + + private val cmdLabel: String = "Ensuring file exists" + + def apply(filePath: String): IO[Unit] = { + val cmd: String = getCmd(filePath) + val label = s"$cmdLabel: ${filePath.split('/').lastOption.getOrElse(filePath)}" + SubStep.withProgress(cmd, label) + } + + private def getCmd(filePath: String): String = { + s"touch $filePath" + } +} diff --git a/federation-controller-backend/project/Dependencies.scala b/federation-controller-backend/project/Dependencies.scala index b578f3ccd1696e04ad74d6a3f051849f791c9bba..1c68b73f83431b5d326a8bada04306d7d0dd0c75 100644 --- a/federation-controller-backend/project/Dependencies.scala +++ b/federation-controller-backend/project/Dependencies.scala @@ -1,4 +1,4 @@ -import sbt._ +import sbt.* // Dependencies // Alphabetical diff --git a/federation-controller-backend/project/metals.sbt b/federation-controller-backend/project/metals.sbt index 89020435fc590a2b98a4e2c46d604038c0e76872..119c92969dd3b38f04693c535f56fec79558ea02 100644 --- a/federation-controller-backend/project/metals.sbt +++ b/federation-controller-backend/project/metals.sbt @@ -1,11 +1,6 @@ // DO NOT EDIT! This file is auto-generated. -// This plugin enables semantic information to be produced by sbt. -// It also adds support for debugging using the Debug Adapter Protocol +// This file enables sbt-bloop to create bloop config files. -addSbtPlugin("org.scalameta" % "sbt-metals" % "1.2.2") - -// This plugin adds the BSP debug capability to sbt server. - -addSbtPlugin("ch.epfl.scala" % "sbt-debug-adapter" % "3.1.6") +addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "1.5.15") diff --git a/federation-controller-backend/src/main/resources/application.conf b/federation-controller-backend/src/main/resources/application.conf index fa60c52e77d174260c31e997e0223624da554d51..bf23dd460ea941e55c32ac65a60be0f15c55e29b 100644 --- a/federation-controller-backend/src/main/resources/application.conf +++ b/federation-controller-backend/src/main/resources/application.conf @@ -19,6 +19,8 @@ app { swarm-key-value = "" swarm-key-value = ${?TRILE_FEDERATION_CONTROLLER_IPFS_SWARM_KEY_VALUE} } + fedvars-absolute-path = "" + fedvars-absolute-path = ${?TRILE_FEDERATION_CONTROLLER_FEDVARS_ABSOLUTE_PATH} } akka-http-cors { diff --git a/federation-controller-backend/src/main/scala/acab/devcon0/boot/ioc/CommonsIoC.scala b/federation-controller-backend/src/main/scala/acab/devcon0/boot/ioc/CommonsIoC.scala index b28ae887bb203ae0d8b7929412d72f929be342d9..2faa2b5e433587ccee0f08425d7183adaaa33fae 100644 --- a/federation-controller-backend/src/main/scala/acab/devcon0/boot/ioc/CommonsIoC.scala +++ b/federation-controller-backend/src/main/scala/acab/devcon0/boot/ioc/CommonsIoC.scala @@ -74,9 +74,13 @@ object CommonsIoC { informationQueryHandler = FederationMemberIoC.Domain.Port.federationMembersInformationQueryHandler ) + val federationRoute: FederationRoute = new FederationRoute(Configuration.configuration) + val p2pRoute: P2pRoute = new P2pRoute(Configuration.configuration.p2p) + val routes: Routes = new Routes( - nodeRoute = nodeRoute, + federationRoute = federationRoute, + federationMemberRoute = nodeRoute, federationMembersRoute = federationMembersRoute, ipfsCidRoute = ipfsCidRoute, p2pRoute = p2pRoute diff --git a/federation-controller-backend/src/main/scala/acab/devcon0/configuration/Configuration.scala b/federation-controller-backend/src/main/scala/acab/devcon0/configuration/Configuration.scala index 24091580f0fe6ebdf04850394ce6672a88307c2c..0e5d9fafe223fd82ac46db70e404e682405537b1 100644 --- a/federation-controller-backend/src/main/scala/acab/devcon0/configuration/Configuration.scala +++ b/federation-controller-backend/src/main/scala/acab/devcon0/configuration/Configuration.scala @@ -32,7 +32,8 @@ final case class Configuration( ipfsCluster: IpfsClusterConfiguration, http: HttpConfiguration, redis: RedisConfiguration, - p2p: P2pConfiguration + p2p: P2pConfiguration, + fedvarsAbsolutePath: String ) object TrileControllerBackendConfigFactory { @@ -70,7 +71,8 @@ object TrileControllerBackendConfigFactory { ipfsCluster = ipfsClusterConfiguration, http = httpConfiguration, redis = redisConfiguration, - p2p = p2pConfiguration + p2p = p2pConfiguration, + fedvarsAbsolutePath = appConfig.getString("fedvars-absolute-path") ) } diff --git a/federation-controller-backend/src/main/scala/acab/devcon0/input/http/FederationMembersRoute.scala b/federation-controller-backend/src/main/scala/acab/devcon0/input/http/FederationMembersRoute.scala index ef96f45599d0009d3a93d317c877d3732f85da4a..4fa816df936da825e180741db3677847af88850a 100644 --- a/federation-controller-backend/src/main/scala/acab/devcon0/input/http/FederationMembersRoute.scala +++ b/federation-controller-backend/src/main/scala/acab/devcon0/input/http/FederationMembersRoute.scala @@ -14,7 +14,7 @@ class FederationMembersRoute(informationQueryHandler: InformationQueryHandler) { private val logger: Logger[IO] = Slf4jLogger.getLogger[IO] - val rootRoutes: HttpRoutes[IO] = { + val routes: HttpRoutes[IO] = { HttpRoutes .of[IO] { case GET -> Root => (for diff --git a/federation-controller-backend/src/main/scala/acab/devcon0/input/http/FederationRoute.scala b/federation-controller-backend/src/main/scala/acab/devcon0/input/http/FederationRoute.scala new file mode 100644 index 0000000000000000000000000000000000000000..a680ee03c9846df45d67ad31e9ec99667c52a5fd --- /dev/null +++ b/federation-controller-backend/src/main/scala/acab/devcon0/input/http/FederationRoute.scala @@ -0,0 +1,60 @@ +package acab.devcon0.input.http + +import acab.devcon0.configuration.Configuration +import acab.devcon0.trile.utils.EffectsUtils +import cats.effect._ +import cats.implicits.catsSyntaxMonadError +import fs2.io.file.Files +import fs2.io.file.Path +import org.http4s._ +import org.http4s.circe.CirceEntityEncoder.circeEntityEncoder +import org.http4s.dsl.io._ +import org.http4s.headers.Authorization +import org.typelevel.log4cats.Logger +import org.typelevel.log4cats.slf4j.Slf4jLogger + +class FederationRoute(configuration: Configuration) { + + implicit val logger: Logger[IO] = Slf4jLogger.getLogger[IO] + + val routes: HttpRoutes[IO] = HttpRoutes + .of[IO] { + case req @ GET -> Root / ".fedvars" => { + validateToken(req) + .flatMap { + case ok @ Response(Status.Ok, _, _, _, _) => { + val filePath: Path = Path(configuration.fedvarsAbsolutePath) + Files[IO] + .readAll(filePath) + .through(fs2.text.utf8.decode) + .compile + .string + .flatMap(fileContent => Ok(fileContent)) + } + case badRequest @ Response(Status.BadRequest, _, _, _, _) => { + IO.pure(badRequest) + } + case unauthorized @ Response(Status.Unauthorized, _, _, _, _) => { + IO.pure(unauthorized) + } + } + .attemptTap(EffectsUtils.attemptTLog) + } + } + + private def validateToken(req: Request[IO]): IO[Response[IO]] = { + val maybeAuthorization: Option[Authorization] = req.headers.get[Authorization] + maybeAuthorization match { + case Some(Authorization(Credentials.Token(AuthScheme.Bearer, token))) => + if isValidToken(token) then { IO(Response(Status.Ok)) } + else { IO(Response(Status.Unauthorized)) } + case _ => { + IO(Response(Status.BadRequest).withEntity("Missing Authorization Header")) + } + } + } + + private def isValidToken(token: String): Boolean = { + true + } +} diff --git a/federation-controller-backend/src/main/scala/acab/devcon0/input/http/Routes.scala b/federation-controller-backend/src/main/scala/acab/devcon0/input/http/Routes.scala index b29e0fb5ee26865456d5b2a0a1aeeca19d66a07e..7041fe02c20ab3d5fd36fe840c9591a385320bd6 100644 --- a/federation-controller-backend/src/main/scala/acab/devcon0/input/http/Routes.scala +++ b/federation-controller-backend/src/main/scala/acab/devcon0/input/http/Routes.scala @@ -6,7 +6,8 @@ import org.http4s.server.Router import org.http4s.server.middleware.CORS class Routes( - nodeRoute: FederationMemberRoute, + federationRoute: FederationRoute, + federationMemberRoute: FederationMemberRoute, federationMembersRoute: FederationMembersRoute, ipfsCidRoute: IpfsCidRoute, p2pRoute: P2pRoute @@ -14,8 +15,9 @@ class Routes( val httpApp: Http[IO, IO] = CORS.policy.withAllowOriginAll( Router( - "/api/federation/members" -> federationMembersRoute.rootRoutes, - "/api/federation/members" -> nodeRoute.routes, + "/api/federation/" -> federationRoute.routes, + "/api/federation/members" -> federationMembersRoute.routes, + "/api/federation/members" -> federationMemberRoute.routes, "/api/ipfs/" -> ipfsCidRoute.routes, "/api/p2p/" -> p2pRoute.routes ).orNotFound diff --git a/trile-backend-commons/src/main/scala/acab/devcon0/trile/Main.scala b/trile-backend-commons/src/main/scala/acab/devcon0/trile/Main.scala deleted file mode 100644 index 6f352eab1d480c43b27c1cee3ed62249fde41828..0000000000000000000000000000000000000000 --- a/trile-backend-commons/src/main/scala/acab/devcon0/trile/Main.scala +++ /dev/null @@ -1,57 +0,0 @@ -package acab.devcon0.trileimport scala.util.Try - -import acab.devcon0.trile.domain.dtos.pubsub.P2p.Message -import io.circe._ -import io.circe.generic.semiauto.deriveDecoder -import io.circe.generic.semiauto.deriveEncoder -import io.circe.jawn.decode -import io.circe.syntax.EncoderOps - -object Main { - - final case class Message[T <: Data](meta: Metadata, data: Data) - final case class Metadata(metadataField: String) - sealed trait Data - final case class DataTypeA(fieldA: String) extends Data - final case class DataTypeB(fieldB: Int) extends Data - - implicit val metadataDecoder: Decoder[Metadata] = deriveDecoder - implicit val dataTypeADecoder: Decoder[DataTypeA] = deriveDecoder - implicit val dataTypeBDecoder: Decoder[DataTypeB] = deriveDecoder - - implicit val metadataEncoder: Encoder[Metadata] = deriveEncoder - implicit val dataTypeAEncoder: Encoder[DataTypeA] = deriveEncoder - implicit val dataTypeBEncoder: Encoder[DataTypeB] = deriveEncoder - - implicit def messageEncoder[T <: Data: Encoder]: Encoder[Message[T]] = deriveEncoder - implicit def messageDecoder[T <: Data: Decoder]: Decoder[Message[T]] = deriveDecoder - - def main(args: Array[String]): Unit = { - - val dataTypeAMessage: Message[DataTypeA] = Message[DataTypeA]( - meta = Metadata(metadataField = "metadata"), - data = DataTypeA("fieldA") - ) - val dataTypeBMessage: Message[DataTypeB] = Message[DataTypeB]( - meta = Metadata(metadataField = "metadata"), - data = DataTypeB(10) - ) - val dataTypeAMessageEncoded = encodeInner[DataTypeA](dataTypeAMessage) - val dataTypeBMessageEncoded = encodeInner[DataTypeB](dataTypeBMessage) - val dataTypeAMessageDecoded = decodeInner[DataTypeA](dataTypeAMessageEncoded) - val dataTypeBMessageDecoded = decodeInner[DataTypeB](dataTypeBMessageEncoded) - - println(s"dataTypeAMessageEncoded=$dataTypeAMessageEncoded") - println(s"dataTypeBMessageEncoded=$dataTypeBMessageEncoded") - println(s"dataTypeAMessageDecoded=$dataTypeAMessageDecoded") - println(s"dataTypeBMessageDecoded=$dataTypeBMessageDecoded") - } - - private def encodeInner[T <: Data: Encoder](message: Message[T]): String = { - EncoderOps[Message[T]](message).asJson.noSpaces - } - - private def decodeInner[T <: Data: Decoder](json: String): Message[T] = { - decode[Message[T]](json).right.get - } -} diff --git a/trile-backend-commons/src/main/scala/acab/devcon0/trile/domain/codecs/IpfsClusterCodecs.scala b/trile-backend-commons/src/main/scala/acab/devcon0/trile/domain/codecs/IpfsClusterCodecs.scala index 895fa4034c1dbe52e6fcc69dc9b84dd5d09dc976..575968b4ca51e9c0222103edfd4915f44499a080 100644 --- a/trile-backend-commons/src/main/scala/acab/devcon0/trile/domain/codecs/IpfsClusterCodecs.scala +++ b/trile-backend-commons/src/main/scala/acab/devcon0/trile/domain/codecs/IpfsClusterCodecs.scala @@ -1,11 +1,16 @@ package acab.devcon0.trile.domain.codecs +import java.time.Instant + import acab.devcon0.trile.domain.dtos import acab.devcon0.trile.domain.dtos.IpfsClusterPeer import acab.devcon0.trile.domain.dtos.IpfsClusterPeerIpfsInfo import acab.devcon0.trile.domain.dtos.IpfsClusterPeers import acab.devcon0.trile.domain.dtos.IpfsClusterPin +import acab.devcon0.trile.domain.dtos.IpfsClusterPinAllocation import acab.devcon0.trile.domain.dtos.IpfsClusterPins +import acab.devcon0.trile.domain.dtos.aliases.IpfsCid +import acab.devcon0.trile.domain.dtos.aliases.IpfsClusterPeerId import cats.effect.IO import io.circe._ import io.circe.generic.semiauto.deriveDecoder @@ -51,6 +56,33 @@ object IpfsClusterCodecs { } } + implicit val allocation: Decoder[IpfsClusterPinAllocation] = (c: HCursor) => { + for + replicationFactorMin <- c.downField("replication_factor_min").as[Int] + replicationFactorMax <- c.downField("replication_factor_max").as[Int] + name <- c.downField("name").as[String] + mode <- c.downField("mode").as[String] + cid <- c.downField("cid").as[IpfsCid] + allocations <- c.downField("allocations").as[Set[IpfsClusterPeerId]] + timestamp <- c.downField("timestamp").as[Instant] + yield { + dtos.IpfsClusterPinAllocation( + replicationFactorMin = replicationFactorMin, + replicationFactorMax = replicationFactorMax, + name = name, + mode = mode, + cid = cid, + allocations = allocations, + timestamp = timestamp + ) + } + } + + object IpfsClusterPinAllocation { + def apply(rawJson: String): IO[IpfsClusterPinAllocation] = + IO.fromEither(decode[IpfsClusterPinAllocation](rawJson)) + } + object IpfsClusterPeerIpfsInfo { def apply(rawJson: String): IO[IpfsClusterPeerIpfsInfo] = IO.fromEither(decode[IpfsClusterPeerIpfsInfo](rawJson)) } diff --git a/trile-backend-commons/src/main/scala/acab/devcon0/trile/domain/dtos/IpfsClusterPeer.scala b/trile-backend-commons/src/main/scala/acab/devcon0/trile/domain/dtos/IpfsClusterPeer.scala index c7f382f6eb1439b12bd6349e7539ce221b72510f..542b66a1d5b33e725552f3e3c12fc0d29b43e121 100644 --- a/trile-backend-commons/src/main/scala/acab/devcon0/trile/domain/dtos/IpfsClusterPeer.scala +++ b/trile-backend-commons/src/main/scala/acab/devcon0/trile/domain/dtos/IpfsClusterPeer.scala @@ -1,5 +1,7 @@ package acab.devcon0.trile.domain.dtos +import java.time.Instant + import acab.devcon0.trile.domain.dtos.aliases.IpfsCid import acab.devcon0.trile.domain.dtos.aliases.IpfsClusterPeerId import acab.devcon0.trile.domain.dtos.aliases.IpfsPeerId @@ -24,3 +26,13 @@ final case class IpfsClusterPeerIpfsInfo( addresses: List[String], error: String ) + +case class IpfsClusterPinAllocation( + replicationFactorMin: Int, + replicationFactorMax: Int, + name: String, + mode: String, + cid: IpfsCid, + allocations: Set[IpfsClusterPeerId], + timestamp: Instant +)