diff --git a/app/uk/gov/hmrc/cataloguefrontend/CatalogueController.scala b/app/uk/gov/hmrc/cataloguefrontend/CatalogueController.scala index 3d59b5fbd..29e764f9b 100644 --- a/app/uk/gov/hmrc/cataloguefrontend/CatalogueController.scala +++ b/app/uk/gov/hmrc/cataloguefrontend/CatalogueController.scala @@ -27,6 +27,7 @@ import play.twirl.api.Html import uk.gov.hmrc.cataloguefrontend.auth.{AuthController, CatalogueAuthBuilders} import uk.gov.hmrc.cataloguefrontend.connector.BuildDeployApiConnector.PrototypeStatus import uk.gov.hmrc.cataloguefrontend.connector.* +import uk.gov.hmrc.cataloguefrontend.connector.RouteRulesConnector.{Route, RouteType} import uk.gov.hmrc.cataloguefrontend.connector.model.RepositoryModules import uk.gov.hmrc.cataloguefrontend.cost.{CostEstimateConfig, CostEstimationService, Zone} import uk.gov.hmrc.cataloguefrontend.leakdetection.LeakDetectionService @@ -37,8 +38,8 @@ import uk.gov.hmrc.cataloguefrontend.serviceconfigs.{ServiceConfigsConnector, Se import uk.gov.hmrc.cataloguefrontend.shuttering.{ShutterService, ShutterState, ShutterType} import uk.gov.hmrc.cataloguefrontend.util.TelemetryLinks import uk.gov.hmrc.cataloguefrontend.servicecommissioningstatus.{LifecycleStatus, ServiceCommissioningStatusConnector} +import uk.gov.hmrc.cataloguefrontend.whatsrunningwhere.{ReleasesConnector, WhatsRunningWhereService} import uk.gov.hmrc.cataloguefrontend.vulnerabilities.VulnerabilitiesConnector -import uk.gov.hmrc.cataloguefrontend.whatsrunningwhere.WhatsRunningWhereService import uk.gov.hmrc.http.HeaderCarrier import uk.gov.hmrc.internalauth.client.{FrontendAuthComponents, IAAction, Predicate, Resource, Retrieval} import uk.gov.hmrc.internalauth.client.Predicate.Permission @@ -63,8 +64,8 @@ class CatalogueController @Inject() ( teamsAndRepositoriesConnector : TeamsAndRepositoriesConnector, serviceConfigsService : ServiceConfigsService, costEstimationService : CostEstimationService, - costEstimateConfig : CostEstimateConfig, routeRulesService : RouteRulesService, + costEstimateConfig : CostEstimateConfig, serviceDependenciesConnector : ServiceDependenciesConnector, serviceCommissioningStatusConnector: ServiceCommissioningStatusConnector, leakDetectionService : LeakDetectionService, @@ -84,6 +85,8 @@ class CatalogueController @Inject() ( repositoryInfoPage : RepositoryInfoPage, serviceMetricsConnector : ServiceMetricsConnector, serviceConfigsConnector : ServiceConfigsConnector, + releasesConnector : ReleasesConnector, + routesRulesConnector : RouteRulesConnector, override val auth : FrontendAuthComponents )(using override val ec: ExecutionContext @@ -193,7 +196,18 @@ class CatalogueController @Inject() ( .map(_.collect { case Some(v) => v }.toMap) latestRepoModules <- serviceDependenciesConnector.getRepositoryModulesLatestVersion(repositoryName) urlIfLeaksFound <- leakDetectionService.urlIfLeaksFound(repositoryName) - serviceRoutes <- routeRulesService.serviceRoutes(serviceName) + routes <- routesRulesConnector.routes(serviceName) + prodApiRoutes <- releasesConnector.apiServices(Environment.Production).map: apiService => + apiService.collect: + case api if api.serviceName == serviceName => + Route( + path = api.context, + ruleConfigurationUrl = None, + routeType = RouteType.ApiContext, + environment = api.environment + ) + allProdRoutes = routes.filter(_.environment == Environment.Production) ++ prodApiRoutes + inconsistentRoutes = routeRulesService.inconsistentRoutes(routes) optLatestServiceInfo <- serviceDependenciesConnector.getSlugInfo(serviceName) serviceCostEstimate <- costEstimationService.estimateServiceCost(serviceName) commenterReport <- prCommenterConnector.report(repositoryName) @@ -224,7 +238,8 @@ class CatalogueController @Inject() ( repositoryCreationDate = repositoryDetails.createdDate, envDatas = optLatestData.fold(envDatas)(envDatas + _), linkToLeakDetection = urlIfLeaksFound, - serviceRoutes = serviceRoutes, + prodRoutes = allProdRoutes, + inconsistentRoutes = inconsistentRoutes, hasBranchProtectionAuth = hasBranchProtectionAuth, commenterReport = commenterReport, serviceRelationships = serviceRelationships, diff --git a/app/uk/gov/hmrc/cataloguefrontend/connector/RouteRulesConnector.scala b/app/uk/gov/hmrc/cataloguefrontend/connector/RouteRulesConnector.scala index 83b1be6b4..e09a03724 100644 --- a/app/uk/gov/hmrc/cataloguefrontend/connector/RouteRulesConnector.scala +++ b/app/uk/gov/hmrc/cataloguefrontend/connector/RouteRulesConnector.scala @@ -17,7 +17,7 @@ package uk.gov.hmrc.cataloguefrontend.connector import play.api.Logger -import play.api.libs.functional.syntax._ +import play.api.libs.functional.syntax.* import play.api.libs.json.{Reads, __} import uk.gov.hmrc.cataloguefrontend.model.{Environment, ServiceName} import uk.gov.hmrc.http.{HeaderCarrier, HttpReads, StringContextOps} @@ -40,92 +40,62 @@ class RouteRulesConnector @Inject() ( private val baseUrl: String = servicesConfig.baseUrl("service-configs") - def frontendServices()(using HeaderCarrier): Future[Seq[String]] = - val url = url"$baseUrl/service-configs/frontend-services" - httpClientV2.get(url) - .execute[Seq[String]] - .recover: - case NonFatal(ex) => - logger.error(s"An error occurred when connecting to $url: ${ex.getMessage}", ex) - Seq.empty - - def frontendRoutes(service: ServiceName)(using HeaderCarrier): Future[Seq[EnvironmentRoute]] = - val url = url"$baseUrl/service-configs/frontend-route/${service.asString}" - given Reads[EnvironmentRoute] = EnvironmentRoute.reads + def routes( + service : ServiceName + , routeType : Option[RouteType] = None + , environment: Option[Environment] = None + )(using + HeaderCarrier + ): Future[Seq[Route]] = + val url = url"$baseUrl/service-configs/routes/${service.asString}?routeType=${routeType.map(_.asString)}&environment=${environment.map(_.asString)}" + given Reads[Route] = Route.reads httpClientV2 .get(url) - .execute[Seq[EnvironmentRoute]] + .execute[Seq[Route]] .recover: case NonFatal(ex) => logger.error(s"An error occurred when connecting to $url: ${ex.getMessage}", ex) Seq.empty - def adminFrontendRoutes(service: ServiceName)(using HeaderCarrier): Future[Seq[EnvironmentRoute]] = - val url = url"$baseUrl/service-configs/admin-frontend-route/${service.asString}" - given Reads[AdminFrontendRoute] = AdminFrontendRoute.reads - httpClientV2 - .get(url) - .execute[Seq[AdminFrontendRoute]] - .map: - _ - .flatMap: raw => - raw.allow.keys.map: env => - EnvironmentRoute( - environment = env - , routes = Seq(Route( - frontendPath = raw.route - , ruleConfigurationUrl = raw.location - , isRegex = false - )) - ) - .groupBy(_.environment) - .toSeq - .map: (k, v) => - EnvironmentRoute(k, v.flatMap(_.routes.sortBy(_.ruleConfigurationUrl)), isAdmin = true) + def frontendServices()(using HeaderCarrier): Future[Seq[String]] = + val url = url"$baseUrl/service-configs/frontend-services" + httpClientV2.get(url) + .execute[Seq[String]] .recover: case NonFatal(ex) => logger.error(s"An error occurred when connecting to $url: ${ex.getMessage}", ex) Seq.empty object RouteRulesConnector: - case class Route( - frontendPath : String - , ruleConfigurationUrl: String - , isRegex : Boolean = false - ) + import uk.gov.hmrc.cataloguefrontend.util.{FromString, FromStringEnum, Parser} + import FromStringEnum._ - case class EnvironmentRoute( - environment: Environment - , routes : Seq[Route] - , isAdmin : Boolean = false - ) - - object EnvironmentRoute: - val reads: Reads[EnvironmentRoute] = - given Reads[Route] = - ( (__ \"frontendPath" ).read[String] - ~ (__ \"ruleConfigurationUrl").read[String] - ~ (__ \"isRegex" ).read[Boolean] - )(Route.apply) + given Parser[RouteType] = Parser.parser(RouteType.values) - ( (__ \"environment").read[Environment] - ~ (__ \"routes" ).read[Seq[Route]] - ~ Reads.pure(false) - )(EnvironmentRoute.apply) + enum RouteType( + val asString : String, + val displayString: String + ) extends FromString + derives Ordering, Reads: + case Frontend extends RouteType(asString = "frontend" , displayString = "Frontend" ) + case AdminFrontend extends RouteType(asString = "adminfrontend", displayString = "Admin Frontend") + case Devhub extends RouteType(asString = "devhub" , displayString = "Devhub" ) + case ApiContext extends RouteType(asString = "apicontext" , displayString = "Api Context" ) - case class AdminFrontendRoute( - service : ServiceName - , route : String - , allow : Map[Environment, List[String]] - , location: String + case class Route( + path : String + , ruleConfigurationUrl: Option[String] + , isRegex : Boolean = false + , routeType : RouteType + , environment : Environment ) - object AdminFrontendRoute: - val reads: Reads[AdminFrontendRoute] = - ( (__ \"service" ).read[ServiceName] - ~ (__ \"route" ).read[String] - ~ (__ \"allow" ).read[Map[Environment, List[String]]] - ~ (__ \"location").read[String] - )(AdminFrontendRoute.apply) - + object Route: + val reads: Reads[Route] = + ( (__ \ "path" ).read[String] + ~ (__ \ "ruleConfigurationUrl").readNullable[String] + ~ (__ \ "isRegex" ).readWithDefault[Boolean](false) + ~ (__ \ "routeType" ).read[RouteType] + ~ (__ \ "environment" ).read[Environment] + )(Route.apply) end RouteRulesConnector diff --git a/app/uk/gov/hmrc/cataloguefrontend/connector/SearchByUrlConnector.scala b/app/uk/gov/hmrc/cataloguefrontend/connector/SearchByUrlConnector.scala index 3ae2a1876..28ced1510 100644 --- a/app/uk/gov/hmrc/cataloguefrontend/connector/SearchByUrlConnector.scala +++ b/app/uk/gov/hmrc/cataloguefrontend/connector/SearchByUrlConnector.scala @@ -42,6 +42,7 @@ class SearchByUrlConnector @Inject() ( ( (__ \ "frontendPath" ).read[String] ~ (__ \ "ruleConfigurationUrl").readWithDefault[String]("") ~ (__ \ "isRegex" ).readWithDefault[Boolean](false) + ~ (__ \ "isDevhub" ).readWithDefault[Boolean](false) )(FrontendRoute.apply) private given Reads[FrontendRoutes] = diff --git a/app/uk/gov/hmrc/cataloguefrontend/service/RouteRulesService.scala b/app/uk/gov/hmrc/cataloguefrontend/service/RouteRulesService.scala index 0cc561032..1642c5562 100644 --- a/app/uk/gov/hmrc/cataloguefrontend/service/RouteRulesService.scala +++ b/app/uk/gov/hmrc/cataloguefrontend/service/RouteRulesService.scala @@ -16,75 +16,29 @@ package uk.gov.hmrc.cataloguefrontend.service -import javax.inject.{Inject, Singleton} -import uk.gov.hmrc.cataloguefrontend.model.{Environment, ServiceName} -import uk.gov.hmrc.cataloguefrontend.connector.RouteRulesConnector -import uk.gov.hmrc.cataloguefrontend.connector.RouteRulesConnector.EnvironmentRoute -import uk.gov.hmrc.http.HeaderCarrier - -import scala.concurrent.{ExecutionContext, Future} - -class RouteRulesService @Inject() ( - routeRulesConnector: RouteRulesConnector -)(using ExecutionContext): - import RouteRulesService._ - - def serviceRoutes(serviceName: ServiceName)(using HeaderCarrier): Future[ServiceRoutes] = - for - frontendRoutes <- routeRulesConnector.frontendRoutes(serviceName) - adminRoutes <- routeRulesConnector.adminFrontendRoutes(serviceName) - yield ServiceRoutes(frontendRoutes ++ adminRoutes) +import javax.inject.Singleton +import uk.gov.hmrc.cataloguefrontend.model.Environment +import uk.gov.hmrc.cataloguefrontend.connector.RouteRulesConnector.{Route, RouteType} @Singleton -object RouteRulesService: - case class ServiceRoutes( - environmentRoutes: Seq[EnvironmentRoute] - ): - private val normalisedEvironmentRoutes: Seq[EnvironmentRoute] = // this should probably be a Map[Environment, Route] - we would need to move isAdmin onto the Route to remove EnvironmentRoute - environmentRoutes - .groupBy(_.environment) - .map: (env, routes) => - EnvironmentRoute( - environment = env - , routes = routes.flatMap(_.routes) - ) - .toSeq - - private[service] val referenceEnvironmentRoutes: Option[EnvironmentRoute] = - normalisedEvironmentRoutes - .find(_.environment == Environment.Production) - .orElse(normalisedEvironmentRoutes.headOption) - .orElse(None) - - private def hasDifferentRoutesToReferenceEnvironment(environmentRoute: EnvironmentRoute, referenceEnvironmentRoute: EnvironmentRoute) = - environmentRoute.routes - .map(_.frontendPath) - .diff(referenceEnvironmentRoute.routes.map(_.frontendPath)) - .nonEmpty - - private def filterRoutesToDifferences(environmentRoute: EnvironmentRoute, referenceEnvironmentRoute: EnvironmentRoute) = - environmentRoute.routes - .filter: r => - environmentRoute.routes - .map(_.frontendPath) - .diff(referenceEnvironmentRoute.routes.map(_.frontendPath)) - .contains(r.frontendPath) - - val inconsistentRoutes: Seq[EnvironmentRoute] = - referenceEnvironmentRoutes - .map: refEnvRoutes => - normalisedEvironmentRoutes - .filter(_.environment != refEnvRoutes.environment) - .filter(environmentRoute => hasDifferentRoutesToReferenceEnvironment(environmentRoute, refEnvRoutes)) - .map(environmentRoute => environmentRoute.copy(routes = filterRoutesToDifferences(environmentRoute, refEnvRoutes))) - .getOrElse(Nil) - - val hasInconsistentRoutes: Boolean = - inconsistentRoutes.nonEmpty - - val isDefined: Boolean = - environmentRoutes.nonEmpty - - end ServiceRoutes - +class RouteRulesService: + + def inconsistentRoutes(routes: Seq[Route]): Seq[Route] = + val referenceRoutes: Seq[Route] = + for + referenceEnv <- routes.map(_.environment).sorted.reverse.headOption.toSeq + routes <- routes.filter(_.environment == referenceEnv) + yield routes + + routes + .filterNot(_.routeType == RouteType.Devhub) // exclude new Devhub route type for now + .groupBy(_.environment) + .collect: + case (_, envRoutes) => + val differentPaths = envRoutes.map(_.path).diff(referenceRoutes.map(_.path)) + envRoutes.filter: r => + differentPaths.contains(r.path) + .flatten + .toSeq + end RouteRulesService diff --git a/app/uk/gov/hmrc/cataloguefrontend/service/SearchByUrlService.scala b/app/uk/gov/hmrc/cataloguefrontend/service/SearchByUrlService.scala index df0f97e81..1a47c037a 100644 --- a/app/uk/gov/hmrc/cataloguefrontend/service/SearchByUrlService.scala +++ b/app/uk/gov/hmrc/cataloguefrontend/service/SearchByUrlService.scala @@ -38,9 +38,10 @@ class SearchByUrlService @Inject() ( )(using HeaderCarrier): Future[Seq[FrontendRoutes]] = if isValidSearchTerm(term) then - searchByUrlConnector - .search(takeUrlPath(term.get)) - .map(_.filter(_.environment == environment)) + searchByUrlConnector.search(takeUrlPath(term.get)).map: searchResults => + searchResults + .filter(_.environment == environment) + .map(r => r.copy(routes = r.routes.filterNot(_.isDevhub))) else Future.successful(Nil) @@ -81,7 +82,8 @@ object SearchByUrlService: case class FrontendRoute( frontendPath : String, ruleConfigurationUrl: String = "", - isRegex : Boolean = false + isRegex : Boolean = false, + isDevhub : Boolean = false, ) case class FrontendRoutes( diff --git a/app/uk/gov/hmrc/cataloguefrontend/shuttering/ShutterService.scala b/app/uk/gov/hmrc/cataloguefrontend/shuttering/ShutterService.scala index d5c297a54..522bc0731 100644 --- a/app/uk/gov/hmrc/cataloguefrontend/shuttering/ShutterService.scala +++ b/app/uk/gov/hmrc/cataloguefrontend/shuttering/ShutterService.scala @@ -17,9 +17,11 @@ package uk.gov.hmrc.cataloguefrontend.shuttering import cats.data.OptionT -import cats.implicits._ +import cats.implicits.* + import javax.inject.{Inject, Singleton} import uk.gov.hmrc.cataloguefrontend.connector.{GitHubProxyConnector, RouteRulesConnector} +import uk.gov.hmrc.cataloguefrontend.connector.RouteRulesConnector.RouteType import uk.gov.hmrc.cataloguefrontend.model.{Environment, ServiceName} import uk.gov.hmrc.internalauth.client.AuthenticatedRequest import uk.gov.hmrc.http.HeaderCarrier @@ -110,11 +112,9 @@ class ShutterService @Inject() ( HeaderCarrier ): Future[Option[String]] = for - baseRoutes <- routeRulesConnector.frontendRoutes(serviceName) - yield - for - envRoute <- baseRoutes.find(_.environment == env).map(_.routes) - frontendRoute <- envRoute.find(_.isRegex == false) - yield ShutterLinkUtils.mkLink(env, frontendRoute.frontendPath) + frontendRoutes <- routeRulesConnector.routes(serviceName, Some(RouteType.Frontend), Some(env)) + shutterRoute = frontendRoutes.find(_.isRegex == false) + yield shutterRoute.map: r => + ShutterLinkUtils.mkLink(env, r.path) end ShutterService diff --git a/app/uk/gov/hmrc/cataloguefrontend/view/ServiceInfoPage.scala.html b/app/uk/gov/hmrc/cataloguefrontend/view/ServiceInfoPage.scala.html index 133fdda05..cf112de06 100644 --- a/app/uk/gov/hmrc/cataloguefrontend/view/ServiceInfoPage.scala.html +++ b/app/uk/gov/hmrc/cataloguefrontend/view/ServiceInfoPage.scala.html @@ -18,7 +18,7 @@ @import uk.gov.hmrc.cataloguefrontend.connector.{GitRepository, JenkinsJob} @import uk.gov.hmrc.cataloguefrontend.prcommenter.PrCommenterReport @import uk.gov.hmrc.cataloguefrontend.cost.{CostEstimateConfig, ServiceCostEstimate} -@import uk.gov.hmrc.cataloguefrontend.service.RouteRulesService.ServiceRoutes +@import uk.gov.hmrc.cataloguefrontend.connector.RouteRulesConnector.{Route, RouteType} @import uk.gov.hmrc.cataloguefrontend.servicecommissioningstatus.Lifecycle @import uk.gov.hmrc.cataloguefrontend.serviceconfigs.ServiceConfigsService @import uk.gov.hmrc.cataloguefrontend.view.ViewMessages @@ -37,7 +37,8 @@ repositoryCreationDate : Instant, envDatas : Map[SlugInfoFlag, EnvData], linkToLeakDetection : Option[String], - serviceRoutes : ServiceRoutes, + prodRoutes : Seq[Route], + inconsistentRoutes : Seq[Route], hasBranchProtectionAuth : EnableBranchProtection.HasAuthorisation, commenterReport : Option[PrCommenterReport], serviceRelationships : ServiceConfigsService.ServiceRelationshipsEnriched, @@ -93,26 +94,18 @@