Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BDOG-3206: displays routes by new route types #956

Merged
merged 6 commits into from
Oct 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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