Skip to content

Commit

Permalink
Merge pull request #956 from hmrc/BDOG-3206
Browse files Browse the repository at this point in the history
BDOG-3206: displays routes by new route types
  • Loading branch information
Tyrpix authored Oct 9, 2024
2 parents 96d9805 + a1f0718 commit 8262d6f
Show file tree
Hide file tree
Showing 17 changed files with 344 additions and 452 deletions.
23 changes: 19 additions & 4 deletions app/uk/gov/hmrc/cataloguefrontend/CatalogueController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down
112 changes: 41 additions & 71 deletions app/uk/gov/hmrc/cataloguefrontend/connector/RouteRulesConnector.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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] =
Expand Down
92 changes: 23 additions & 69 deletions app/uk/gov/hmrc/cataloguefrontend/service/RouteRulesService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Loading

0 comments on commit 8262d6f

Please sign in to comment.