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

feat: Configurations load improvement #954

Merged
merged 6 commits into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ lazy val V = new {

// https://mvnrepository.com/artifact/dev.zio/zio
val zio = "2.0.18"
val zioConfig = "3.0.7"
val zioConfig = "4.0.1"
val zioLogging = "2.1.16"
val zioJson = "0.3.0"
val zioHttp = "3.0.0-RC4"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,21 @@ import io.iohk.atala.prism.protos.node_api.NodeServiceGrpc
import io.iohk.atala.shared.db.{ContextAwareTask, DbConfig, TransactorLayer}
import org.keycloak.authorization.client.AuthzClient
import zio.*
import zio.config.typesafe.TypesafeConfigSource
import zio.config.{ReadError, read}
import zio.config.typesafe.TypesafeConfigProvider
import zio.http.Client

object SystemModule {
val configLayer: Layer[ReadError[String], AppConfig] = ZLayer.fromZIO {
read(
AppConfig.descriptor.from(
TypesafeConfigSource.fromTypesafeConfig(
ZIO.attempt(ConfigFactory.load())
)
)
)
}

private val tmp: IO[Config.Error, AppConfig] =
for {
ret: AppConfig <- TypesafeConfigProvider
.fromTypesafeConfig(ConfigFactory.load())
.load(AppConfig.config)
_ <- ZIO.log(s"HTTP server endpoint is setup as '${ret.agent.httpEndpoint.publicEndpointUrl}'")
_ <- ZIO.log(s"DIDComm server endpoint is setup as '${ret.agent.didCommEndpoint.publicEndpointUrl}'")
} yield ret

val configLayer = ZLayer.fromZIO(tmp)

val zioHttpClientLayer: ZLayer[Any, Throwable, Client] = {
import zio.http.netty.NettyConfig
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import io.iohk.atala.iam.entity.http.EntityServerEndpoints
import io.iohk.atala.iam.wallet.http.WalletManagementServerEndpoints
import io.iohk.atala.issue.controller.IssueServerEndpoints
import io.iohk.atala.mercury.{DidOps, HttpClient}
import io.iohk.atala.pollux.core.service.{CredentialService, CredentialStatusListService, PresentationService}
import io.iohk.atala.pollux.core.service.{CredentialService, PresentationService}
import io.iohk.atala.pollux.credentialdefinition.CredentialDefinitionRegistryServerEndpoints
import io.iohk.atala.pollux.credentialschema.{SchemaRegistryServerEndpoints, VerificationPolicyServerEndpoints}
import io.iohk.atala.pollux.vc.jwt.DidResolver as JwtDidResolver
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import io.iohk.atala.castor.core.model.did.VerificationRelationship
import io.iohk.atala.iam.authentication.AuthenticationConfig
import io.iohk.atala.pollux.vc.jwt.*
import io.iohk.atala.shared.db.DbConfig
import zio.Config
import zio.config.*
import zio.config.magnolia.Descriptor
import zio.config.magnolia.*
import java.net.URL
import java.time.Duration
import scala.util.Try
Expand All @@ -23,7 +24,18 @@ final case class AppConfig(
}

object AppConfig {
val descriptor: ConfigDescriptor[AppConfig] = Descriptor[AppConfig]
given Config[java.net.URL] = Config.string.mapOrFail(url =>
val urlRegex = """^(http|https)://[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(:[0-9]{1,5})?(/.*)?$""".r
urlRegex.findFirstMatchIn(url) match
case Some(_) =>
Try(java.net.URL(url)).toEither.left.map(ex /*java.net.MalformedURLException*/ =>
Config.Error.InvalidData(zio.Chunk.empty, ex.getMessage())
)
case _ => Left(Config.Error.InvalidData(zio.Chunk.empty, s"Invalid URL: $url"))
)

val config: Config[AppConfig] = deriveConfig[AppConfig]

}

final case class VaultConfig(
Expand Down Expand Up @@ -77,9 +89,7 @@ final case class PrismNodeConfig(service: GrpcServiceConfig)

final case class GrpcServiceConfig(host: String, port: Int, usePlainText: Boolean)

final case class StatusListRegistryConfig(
publicEndpointUrl: String
)
final case class StatusListRegistryConfig(publicEndpointUrl: java.net.URL)

final case class DatabaseConfig(
host: String,
Expand Down Expand Up @@ -169,28 +179,13 @@ final case class AgentConfig(
"The default wallet must be enabled if all the authentication methods are disabled. Default wallet is required for the single-tenant mode."
)
_ <- secretStorage.validate
_ <- httpEndpoint.validate
_ <- didCommEndpoint.validate
} yield ()

}

object EndpointConfig {
def validate(url: String): Either[String, Unit] = {
val urlRegex = """^(http|https)://[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(:[0-9]{1,5})?(/.*)?$""".r
urlRegex.findFirstMatchIn(url) match
case Some(_) => Right(())
case _ => Left(s"Invalid URL: $url")
}
}

final case class HttpEndpointConfig(http: HttpConfig, publicEndpointUrl: String) {
def validate: Either[String, Unit] = EndpointConfig.validate(publicEndpointUrl)
}
final case class HttpEndpointConfig(http: HttpConfig, publicEndpointUrl: java.net.URL)

final case class DidCommEndpointConfig(http: HttpConfig, publicEndpointUrl: String) {
def validate: Either[String, Unit] = EndpointConfig.validate(publicEndpointUrl)
}
final case class DidCommEndpointConfig(http: HttpConfig, publicEndpointUrl: java.net.URL)

final case class HttpConfig(port: Int)

Expand All @@ -214,15 +209,3 @@ final case class SecretStorageConfig(
enum SecretStorageBackend {
case vault, postgres, memory
}

object SecretStorageBackend {
given Descriptor[SecretStorageBackend] =
Descriptor.from(
Descriptor[String].transformOrFailLeft { s =>
Try(SecretStorageBackend.valueOf(s)).toOption
.toRight(
s"Invalid configuration value '$s'. Possible values: ${SecretStorageBackend.values.mkString("[", ", ", "]")}"
)
}(_.toString())
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ object IssueBackgroundJobs extends BackgroundJobsHelper {
credentialService <- ZIO.service[CredentialService]
config <- ZIO.service[AppConfig]
_ <- credentialService
.generateJWTCredential(id, config.pollux.statusListRegistry.publicEndpointUrl)
.generateJWTCredential(id, config.pollux.statusListRegistry.publicEndpointUrl.toExternalForm)
.provideSomeLayer(ZLayer.succeed(walletAccessContext))
} yield ()).mapError(e => (walletAccessContext, e))
} yield result
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class IssueControllerImpl(
ErrorResponse.badRequest(detail = Some("Missing request parameter: credentialDefinitionId"))
)
credentialDefinitionId = {
val publicEndpointUrl = appConfig.agent.httpEndpoint.publicEndpointUrl
val publicEndpointUrl = appConfig.agent.httpEndpoint.publicEndpointUrl.toExternalForm
val urlSuffix =
s"credential-definition-registry/definitions/${credentialDefinitionGUID.toString}/definition"
val urlPrefix = if (publicEndpointUrl.endsWith("/")) publicEndpointUrl else publicEndpointUrl + "/"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import zio.test.*
import zio.test.Assertion.*
import zio.test.ZIOSpecDefault

/** prismAgentServer/testOnly io.iohk.atala.agent.server.config.AppConfigSpec */
object AppConfigSpec extends ZIOSpecDefault {

private val baseVaultConfig = VaultConfig(
Expand All @@ -16,7 +17,7 @@ object AppConfigSpec extends ZIOSpecDefault {
appRoleSecretId = None,
useSemanticPath = true,
)
private val baseInvalidHttpEndpointConfig = "http://:8080"
// private val baseInvalidHttpEndpointConfig = java.net.URL("http://:8080")

override def spec = suite("AppConfigSpec")(
test("load config successfully") {
Expand Down Expand Up @@ -75,26 +76,71 @@ object AppConfigSpec extends ZIOSpecDefault {
)
assert(vaultConfig.validate)(isRight(isSubtype[ValidatedVaultConfig.TokenAuth](anything)))
},
test("reject config when invalid http endpoint config provided") {
for {
appConfig <- ZIO.serviceWith[AppConfig](
_.focus(_.agent.secretStorage.backend)
.replace(SecretStorageBackend.memory)
.focus(_.agent.httpEndpoint.publicEndpointUrl)
.replace(baseInvalidHttpEndpointConfig)
)
} yield assert(appConfig.validate)(isLeft(containsString("Invalid URL: http://:8080")))
},
test("reject config when invalid http didcomm service endpoint url provided") {
for {
appConfig <- ZIO.serviceWith[AppConfig](
_.focus(_.agent.secretStorage.backend)
.replace(SecretStorageBackend.memory)
.focus(_.agent.didCommEndpoint.publicEndpointUrl)
.replace(baseInvalidHttpEndpointConfig)
).provide(SystemModule.configLayer) + {

import AppConfig.given
import zio.config.magnolia.*
val didCommEndpointConfig: Config[DidCommEndpointConfig] = deriveConfig[DidCommEndpointConfig]

suite("DidCommEndpointConfig URL type")(
test("DidCommEndpointConfig that the correct format") {
{
for {
didCommEndpointConfig <- ZIO.service[DidCommEndpointConfig]
} yield assert(true)(isTrue)
}.provide(
ZLayer.fromZIO(
ConfigProvider
.fromMap(Map("http.port" -> "9999", "publicEndpointUrl" -> "http://example:8080/path"))
.load(didCommEndpointConfig)
)
)
} yield assert(appConfig.validate)(isLeft(containsString("Invalid URL: http://:8080")))
},
).provide(SystemModule.configLayer)
},
test("reject config when invalid http didcomm service endpoint url provided") {

assertZIO(
ConfigProvider
.fromMap(Map("http.port" -> "9999", "publicEndpointUrl" -> "http://:8080/path"))
.load(didCommEndpointConfig)
.exit
)(fails(isSubtype[Config.Error.InvalidData](anything)))
// Config.Error.InvalidData(zio.Chunk("publicEndpointUrl"), "Invalid URL: http://:8080/path")
},
)
} + {
import zio.config.magnolia.deriveConfig
case class TestSecretStorageBackend(t: SecretStorageBackend)
val secretStorageBackendConfig: Config[TestSecretStorageBackend] = deriveConfig[TestSecretStorageBackend]

suite("SecretStorageBackend enum test deriveConfig")(
test("test SecretStorageBackend is postgres") {
{
for {
secretStorageBackend <- ZIO.service[TestSecretStorageBackend]
_ <- ZIO.log(secretStorageBackend.toString())
} yield assertTrue(secretStorageBackend.t == SecretStorageBackend.postgres)
}.provide(
ZLayer.fromZIO(
ConfigProvider
.fromMap(Map("t" -> "postgres"))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw @patlo-iog I had the test just to make sure that this works as expected

.load(secretStorageBackendConfig)
)
)
},
test("test SecretStorageBackend is not vault") {
{
for {
secretStorageBackend <- ZIO.service[TestSecretStorageBackend]
_ <- ZIO.log(secretStorageBackend.toString())
} yield assertTrue(secretStorageBackend.t != SecretStorageBackend.vault)
}.provide(
ZLayer.fromZIO(
ConfigProvider
.fromMap(Map("t" -> "postgres"))
.load(secretStorageBackendConfig)
)
)
},
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ import sttp.tapir.server.interceptor.CustomiseInterceptors
import sttp.tapir.server.stub.TapirStubInterpreter
import sttp.tapir.ztapir.RIOMonadError
import zio.*
import zio.config.typesafe.TypesafeConfigSource
import zio.config.{ReadError, read}
import zio.config.typesafe.TypesafeConfigProvider
import zio.json.ast.Json
import zio.json.ast.Json.*
import zio.test.*
Expand All @@ -54,15 +53,11 @@ trait IssueControllerTestTools extends PostgresTestContainerSupport {
]
val didResolverLayer = ZLayer.fromZIO(ZIO.succeed(makeResolver(Map.empty)))

val configLayer: Layer[ReadError[String], AppConfig] = ZLayer.fromZIO {
read(
AppConfig.descriptor.from(
TypesafeConfigSource.fromTypesafeConfig(
ZIO.attempt(ConfigFactory.load())
)
)
)
}
val configLayer = ZLayer.fromZIO(
TypesafeConfigProvider
.fromTypesafeConfig(ConfigFactory.load())
.load(AppConfig.config)
)

private[this] def makeResolver(lookup: Map[String, DIDDocument]): DidResolver = (didUrl: String) => {
lookup
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ trait ManagedDIDService {
): ZIO[WalletAccessContext, UpdateManagedDIDError, ScheduleDIDOperationOutcome]

/** PeerDID related methods */
def createAndStorePeerDID(serviceEndpoint: String): URIO[WalletAccessContext, PeerDID]
def createAndStorePeerDID(serviceEndpoint: java.net.URL): URIO[WalletAccessContext, PeerDID]

def getPeerDID(didId: DidId): ZIO[WalletAccessContext, DIDSecretStorageError.KeyNotFoundError, PeerDID]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -345,9 +345,9 @@ class ManagedDIDServiceImpl private[walletapi] (

/** PeerDID related methods
*/
def createAndStorePeerDID(serviceEndpoint: String): URIO[WalletAccessContext, PeerDID] =
def createAndStorePeerDID(serviceEndpoint: java.net.URL): URIO[WalletAccessContext, PeerDID] =
for {
peerDID <- ZIO.succeed(PeerDID.makePeerDid(serviceEndpoint = Some(serviceEndpoint)))
peerDID <- ZIO.succeed(PeerDID.makePeerDid(serviceEndpoint = Some(serviceEndpoint.toExternalForm())))
_ <- nonSecretStorage.createPeerDIDRecord(peerDID.did).orDie
_ <- secretStorage.insertKey(peerDID.did, AGREEMENT_KEY_ID, peerDID.jwkForKeyAgreement).orDie
_ <- secretStorage.insertKey(peerDID.did, AUTHENTICATION_KEY_ID, peerDID.jwkForKeyAuthentication).orDie
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ object MockManagedDIDService extends Mock[ManagedDIDService] {
): IO[UpdateManagedDIDError, ScheduleDIDOperationOutcome] = ???

override def createAndStorePeerDID(
serviceEndpoint: String
serviceEndpoint: java.net.URL
): UIO[PeerDID] = ???

override def getPeerDID(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,8 +320,9 @@ object ManagedDIDServiceSpec
ctx2 = ZLayer.succeed(WalletAccessContext(walletId2))
svc <- ZIO.service[ManagedDIDService]
storage <- ZIO.service[DIDNonSecretStorage]
peerDid1 <- svc.createAndStorePeerDID("http://example.com").provide(ctx1)
peerDid2 <- svc.createAndStorePeerDID("http://example.com").provide(ctx2)
urlTmp = java.net.URL("http://example.com")
peerDid1 <- svc.createAndStorePeerDID(urlTmp).provide(ctx1)
peerDid2 <- svc.createAndStorePeerDID(urlTmp).provide(ctx2)
record1 <- storage.getPeerDIDRecord(peerDid1.did)
record2 <- storage.getPeerDIDRecord(peerDid2.did)
} yield {
Expand Down Expand Up @@ -514,8 +515,9 @@ object ManagedDIDServiceSpec
ctx1 = ZLayer.succeed(WalletAccessContext(walletId1))
ctx2 = ZLayer.succeed(WalletAccessContext(walletId2))
svc <- ZIO.service[ManagedDIDService]
dids1 <- ZIO.foreach(1 to 3)(_ => svc.createAndStorePeerDID("http://example.com")).provide(ctx1)
dids2 <- ZIO.foreach(1 to 3)(_ => svc.createAndStorePeerDID("http://example.com")).provide(ctx2)
urlTmp = java.net.URL("http://example.com")
dids1 <- ZIO.foreach(1 to 3)(_ => svc.createAndStorePeerDID(urlTmp)).provide(ctx1)
dids2 <- ZIO.foreach(1 to 3)(_ => svc.createAndStorePeerDID(urlTmp)).provide(ctx2)
ownWalletDids1 <- ZIO.foreach(dids1)(d => svc.getPeerDID(d.did).exit).provide(ctx1)
ownWalletDids2 <- ZIO.foreach(dids2)(d => svc.getPeerDID(d.did).exit).provide(ctx2)
crossWalletDids1 <- ZIO.foreach(dids1)(d => svc.getPeerDID(d.did).exit).provide(ctx2)
Expand Down
Loading