diff --git a/services/build.gradle.kts b/services/build.gradle.kts index 21ac23854ab..15391f17e2f 100644 --- a/services/build.gradle.kts +++ b/services/build.gradle.kts @@ -90,21 +90,20 @@ subprojects { if (project.name == "s3") { dependencies { - // FIXME SDK-KT-214 or re-enable after next release -// val services = project.parent?.subprojects -// -// if (services?.any { it.name == "s3control" } == true) { -// implementation(project(":services:s3control")) -// } else { -// implementation("aws.sdk.kotlin:s3control:+") -// } -// -// if (services?.any { it.name == "sts" } == true) { -// implementation(project(":services:sts")) -// } else { -// implementation("aws.sdk.kotlin:sts:+") -// } -// implementation(libs.smithy.kotlin.aws.signing.crt) + val services = project.parent?.subprojects + + if (services?.any { it.name == "s3control" } == true) { + implementation(project(":services:s3control")) + } else { + implementation("aws.sdk.kotlin:s3control:+") + } + + if (services?.any { it.name == "sts" } == true) { + implementation(project(":services:sts")) + } else { + implementation("aws.sdk.kotlin:sts:+") + } + implementation(libs.smithy.kotlin.aws.signing.crt) } } diff --git a/services/s3/e2eTest/src/MutliRegionAccessPointTest.kt b/services/s3/e2eTest/src/MutliRegionAccessPointTest.kt index 152dab1bbd5..9c3cb087fa6 100644 --- a/services/s3/e2eTest/src/MutliRegionAccessPointTest.kt +++ b/services/s3/e2eTest/src/MutliRegionAccessPointTest.kt @@ -4,112 +4,106 @@ */ package aws.sdk.kotlin.e2etest -class MutliRegionAccessPointTest +import aws.sdk.kotlin.e2etest.S3TestUtils.createMultiRegionAccessPoint +import aws.sdk.kotlin.e2etest.S3TestUtils.deleteBucketAndAllContents +import aws.sdk.kotlin.e2etest.S3TestUtils.deleteMultiRegionAccessPoint +import aws.sdk.kotlin.e2etest.S3TestUtils.getAccountId +import aws.sdk.kotlin.e2etest.S3TestUtils.getMultiRegionAccessPointArn +import aws.sdk.kotlin.e2etest.S3TestUtils.getTestBucket +import aws.sdk.kotlin.e2etest.S3TestUtils.multiRegionAccessPointWasCreated +import aws.sdk.kotlin.services.s3.S3Client +import aws.sdk.kotlin.services.s3.deleteObject +import aws.sdk.kotlin.services.s3.putObject +import aws.sdk.kotlin.services.s3.withConfig +import aws.sdk.kotlin.services.s3control.S3ControlClient +import aws.smithy.kotlin.runtime.auth.awssigning.UnsupportedSigningAlgorithmException +import aws.smithy.kotlin.runtime.auth.awssigning.crt.CrtAwsSigner +import aws.smithy.kotlin.runtime.http.auth.SigV4AsymmetricAuthScheme +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.AfterAll +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.TestInstance +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith -// FIXME SDK-KT-214 or re-enable after next release -// -// import aws.sdk.kotlin.e2etest.S3TestUtils.createMultiRegionAccessPoint -// import aws.sdk.kotlin.e2etest.S3TestUtils.deleteBucketAndAllContents -// import aws.sdk.kotlin.e2etest.S3TestUtils.deleteMultiRegionAccessPoint -// import aws.sdk.kotlin.e2etest.S3TestUtils.getAccountId -// import aws.sdk.kotlin.e2etest.S3TestUtils.getMultiRegionAccessPointArn -// import aws.sdk.kotlin.e2etest.S3TestUtils.getTestBucket -// import aws.sdk.kotlin.e2etest.S3TestUtils.multiRegionAccessPointWasCreated -// import aws.sdk.kotlin.services.s3.S3Client -// import aws.sdk.kotlin.services.s3.deleteObject -// import aws.sdk.kotlin.services.s3.putObject -// import aws.sdk.kotlin.services.s3.withConfig -// import aws.sdk.kotlin.services.s3control.S3ControlClient -// import aws.smithy.kotlin.runtime.auth.awssigning.UnsupportedSigningAlgorithmException -// import aws.smithy.kotlin.runtime.auth.awssigning.crt.CrtAwsSigner -// import aws.smithy.kotlin.runtime.http.auth.SigV4AsymmetricAuthScheme -// import kotlinx.coroutines.runBlocking -// import org.junit.jupiter.api.AfterAll -// import org.junit.jupiter.api.BeforeAll -// import org.junit.jupiter.api.Disabled -// import org.junit.jupiter.api.TestInstance -// import org.junit.jupiter.api.condition.EnabledIfSystemProperty -// import kotlin.test.Test -// import kotlin.test.assertEquals -// import kotlin.test.assertFailsWith -// -// @TestInstance(TestInstance.Lifecycle.PER_CLASS) -// class MutliRegionAccessPointTest { -// private val s3West = S3Client { region = "us-west-2" } -// private val s3East = s3West.withConfig { region = "us-east-2" } -// private val s3SigV4a = s3West.withConfig { authSchemes = listOf(SigV4AsymmetricAuthScheme(CrtAwsSigner)) } -// private val s3Control = S3ControlClient { region = "us-west-2" } -// -// private val multiRegionAccessPoint = "aws-sdk-for-kotlin-test-multi-region-access-point" -// private val objectKey = "test.txt" -// -// private lateinit var accountId: String -// private lateinit var multiRegionAccessPointArn: String -// private lateinit var usWestBucket: String -// private lateinit var usEastBucket: String -// -// @BeforeAll -// private fun setUp(): Unit = runBlocking { -// accountId = getAccountId() -// usWestBucket = getTestBucket(s3West, "us-west-2", accountId) -// usEastBucket = getTestBucket(s3East, "us-east-2", accountId) -// -// createMultiRegionAccessPoint( -// s3Control, -// multiRegionAccessPoint, -// usWestBucket, -// usEastBucket, -// accountId, -// ) -// -// multiRegionAccessPointArn = -// getMultiRegionAccessPointArn( -// s3Control, -// multiRegionAccessPoint, -// accountId, -// ) -// } -// -// @AfterAll -// private fun cleanUp(): Unit = runBlocking { -// if (multiRegionAccessPointWasCreated(s3Control, multiRegionAccessPoint, accountId)) { -// deleteMultiRegionAccessPoint(s3Control, multiRegionAccessPoint, accountId) -// } -// -// deleteBucketAndAllContents(s3West, usWestBucket) -// deleteBucketAndAllContents(s3East, usEastBucket) -// -// s3West.close() -// s3East.close() -// s3SigV4a.close() -// s3Control.close() -// } -// -// @Test -// fun testMultiRegionAccessPointOperation(): Unit = runBlocking { -// s3SigV4a.putObject { -// bucket = multiRegionAccessPointArn -// key = objectKey -// } -// -// s3SigV4a.deleteObject { -// bucket = multiRegionAccessPointArn -// key = objectKey -// } -// } -// -// @Test -// fun testUnsupportedSigningAlgorithm(): Unit = runBlocking { -// val ex = assertFailsWith { -// s3West.putObject { -// bucket = multiRegionAccessPointArn -// key = objectKey -// } -// } -// -// assertEquals( -// ex.message, -// "SIGV4A support is not yet implemented for the default signer. For more information on how to enable it with the CRT signer, please refer to: https://a.co/3sf8533", -// ) -// } -// } +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class MutliRegionAccessPointTest { + private val s3West = S3Client { region = "us-west-2" } + private val s3East = s3West.withConfig { region = "us-east-2" } + private val s3SigV4a = s3West.withConfig { authSchemes = listOf(SigV4AsymmetricAuthScheme(CrtAwsSigner)) } + private val s3Control = S3ControlClient { region = "us-west-2" } + + private val multiRegionAccessPoint = "aws-sdk-for-kotlin-test-multi-region-access-point" + private val objectKey = "test.txt" + + private lateinit var accountId: String + private lateinit var multiRegionAccessPointArn: String + private lateinit var usWestBucket: String + private lateinit var usEastBucket: String + + @BeforeAll + private fun setUp(): Unit = runBlocking { + accountId = getAccountId() + usWestBucket = getTestBucket(s3West, "us-west-2", accountId) + usEastBucket = getTestBucket(s3East, "us-east-2", accountId) + + createMultiRegionAccessPoint( + s3Control, + multiRegionAccessPoint, + usWestBucket, + usEastBucket, + accountId, + ) + + multiRegionAccessPointArn = + getMultiRegionAccessPointArn( + s3Control, + multiRegionAccessPoint, + accountId, + ) + } + + @AfterAll + private fun cleanUp(): Unit = runBlocking { + if (multiRegionAccessPointWasCreated(s3Control, multiRegionAccessPoint, accountId)) { + deleteMultiRegionAccessPoint(s3Control, multiRegionAccessPoint, accountId) + } + + deleteBucketAndAllContents(s3West, usWestBucket) + deleteBucketAndAllContents(s3East, usEastBucket) + + s3West.close() + s3East.close() + s3SigV4a.close() + s3Control.close() + } + + @Test + fun testMultiRegionAccessPointOperation(): Unit = runBlocking { + s3SigV4a.putObject { + bucket = multiRegionAccessPointArn + key = objectKey + } + + s3SigV4a.deleteObject { + bucket = multiRegionAccessPointArn + key = objectKey + } + } + + @Test + fun testUnsupportedSigningAlgorithm(): Unit = runBlocking { + val ex = assertFailsWith { + s3West.putObject { + bucket = multiRegionAccessPointArn + key = objectKey + } + } + + assertEquals( + ex.message, + "SIGV4A support is not yet implemented for the default signer. For more information on how to enable it with the CRT signer, please refer to: https://a.co/3sf8533", + ) + } +} diff --git a/services/s3/e2eTest/src/S3TestUtils.kt b/services/s3/e2eTest/src/S3TestUtils.kt index fb75f65b499..91f8b846261 100644 --- a/services/s3/e2eTest/src/S3TestUtils.kt +++ b/services/s3/e2eTest/src/S3TestUtils.kt @@ -14,6 +14,9 @@ import aws.sdk.kotlin.services.s3.model.LifecycleRuleFilter import aws.sdk.kotlin.services.s3.paginators.listObjectsV2Paginated import aws.sdk.kotlin.services.s3.waiters.waitUntilBucketExists import aws.sdk.kotlin.services.s3.waiters.waitUntilBucketNotExists +import aws.sdk.kotlin.services.s3control.* +import aws.sdk.kotlin.services.s3control.model.* +import aws.sdk.kotlin.services.sts.StsClient import aws.smithy.kotlin.runtime.http.request.HttpRequest import aws.smithy.kotlin.runtime.text.ensurePrefix import kotlinx.coroutines.* @@ -23,6 +26,8 @@ import java.io.OutputStreamWriter import java.net.URL import java.util.* import javax.net.ssl.HttpsURLConnection +import kotlin.time.Duration +import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.seconds object S3TestUtils { @@ -185,127 +190,129 @@ object S3TestUtils { return connection.responseCode } -// internal suspend fun getAccountId(): String { -// println("Getting account ID") -// -// val accountId = StsClient { -// region = "us-west-2" -// }.use { -// it.getCallerIdentity().account -// } -// -// return checkNotNull(accountId) { "Unable to get AWS account ID" } -// } -// -// internal suspend fun createMultiRegionAccessPoint( -// s3ControlClient: S3ControlClient, -// multiRegionAccessPointName: String, -// regionOneBucket: String, -// regionTwoBucket: String, -// testAccountId: String, -// ) { -// println("Creating multi region access point: $multiRegionAccessPointName") -// -// val createRequestToken = s3ControlClient.createMultiRegionAccessPoint { -// accountId = testAccountId -// details { -// name = multiRegionAccessPointName -// regions = listOf( -// Region { bucket = regionOneBucket }, -// Region { bucket = regionTwoBucket }, -// ) -// } -// } -// -// waitUntilMultiRegionAccessPointOperationCompletes( -// s3ControlClient, -// checkNotNull(createRequestToken.requestTokenArn) { "Unable to get request token ARN" }, -// 10.minutes, -// testAccountId, -// "createMultiRegionAccessPoint", -// ) -// } -// -// internal suspend fun getMultiRegionAccessPointArn( -// s3ControlClient: S3ControlClient, -// multiRegionAccessPointName: String, -// testAccountId: String, -// ): String { -// println("Getting multi region access point arn for: $multiRegionAccessPointName") -// -// s3ControlClient.getMultiRegionAccessPoint { -// accountId = testAccountId -// name = multiRegionAccessPointName -// }.accessPoint?.alias?.let { alias -> -// return "arn:aws:s3::$testAccountId:accesspoint/$alias" -// } -// throw Exception("Unable to get multi region access point arn") -// } -// -// internal suspend fun deleteMultiRegionAccessPoint( -// s3ControlClient: S3ControlClient, -// multiRegionAccessPointName: String, -// testAccountId: String, -// ) { -// println("Deleting multi region access point: $multiRegionAccessPointName") -// -// val deleteRequestToken = s3ControlClient.deleteMultiRegionAccessPoint { -// accountId = testAccountId -// details { -// name = multiRegionAccessPointName -// } -// } -// -// waitUntilMultiRegionAccessPointOperationCompletes( -// s3ControlClient, -// checkNotNull(deleteRequestToken.requestTokenArn) { "Unable to get request token ARN" }, -// 5.minutes, -// testAccountId, -// "deleteMultiRegionAccessPoint", -// ) -// } -// -// private suspend fun waitUntilMultiRegionAccessPointOperationCompletes( -// s3ControlClient: S3ControlClient, -// request: String, -// timeoutAfter: Duration, -// testAccountId: String, -// operation: String, -// ) { -// withTimeout(timeoutAfter) { -// var status: String? = null -// while (true) { -// val latestStatus = s3ControlClient.describeMultiRegionAccessPointOperation { -// accountId = testAccountId -// requestTokenArn = request -// }.asyncOperation?.requestStatus -// -// when (latestStatus) { -// "SUCCEEDED" -> { -// println("$operation operation succeeded.") -// return@withTimeout -// } -// "FAILED" -> throw IllegalStateException("$operation operation failed") -// else -> { if (status == null || latestStatus != status) { -// println("Waiting on $operation operation. Status: $latestStatus ") -// status = latestStatus -// } } -// } -// -// delay(10.seconds) // Avoid constant status checks -// } -// } -// } -// -// internal suspend fun multiRegionAccessPointWasCreated( -// s3Control: S3ControlClient, -// multiRegionAccessPointName: String, -// testAccountId: String, -// ): Boolean { -// println("Checking if multi region access point was created: $multiRegionAccessPointName") -// -// return s3Control.listMultiRegionAccessPoints { -// accountId = testAccountId -// }.accessPoints?.any { it.name == multiRegionAccessPointName } ?: false -// } + internal suspend fun getAccountId(): String { + println("Getting account ID") + + val accountId = StsClient { + region = "us-west-2" + }.use { + it.getCallerIdentity().account + } + + return checkNotNull(accountId) { "Unable to get AWS account ID" } + } + + internal suspend fun createMultiRegionAccessPoint( + s3ControlClient: S3ControlClient, + multiRegionAccessPointName: String, + regionOneBucket: String, + regionTwoBucket: String, + testAccountId: String, + ) { + println("Creating multi region access point: $multiRegionAccessPointName") + + val createRequestToken = s3ControlClient.createMultiRegionAccessPoint { + accountId = testAccountId + details { + name = multiRegionAccessPointName + regions = listOf( + Region { bucket = regionOneBucket }, + Region { bucket = regionTwoBucket }, + ) + } + } + + waitUntilMultiRegionAccessPointOperationCompletes( + s3ControlClient, + checkNotNull(createRequestToken.requestTokenArn) { "Unable to get request token ARN" }, + 10.minutes, + testAccountId, + "createMultiRegionAccessPoint", + ) + } + + internal suspend fun getMultiRegionAccessPointArn( + s3ControlClient: S3ControlClient, + multiRegionAccessPointName: String, + testAccountId: String, + ): String { + println("Getting multi region access point arn for: $multiRegionAccessPointName") + + s3ControlClient.getMultiRegionAccessPoint { + accountId = testAccountId + name = multiRegionAccessPointName + }.accessPoint?.alias?.let { alias -> + return "arn:aws:s3::$testAccountId:accesspoint/$alias" + } + throw Exception("Unable to get multi region access point arn") + } + + internal suspend fun deleteMultiRegionAccessPoint( + s3ControlClient: S3ControlClient, + multiRegionAccessPointName: String, + testAccountId: String, + ) { + println("Deleting multi region access point: $multiRegionAccessPointName") + + val deleteRequestToken = s3ControlClient.deleteMultiRegionAccessPoint { + accountId = testAccountId + details { + name = multiRegionAccessPointName + } + } + + waitUntilMultiRegionAccessPointOperationCompletes( + s3ControlClient, + checkNotNull(deleteRequestToken.requestTokenArn) { "Unable to get request token ARN" }, + 5.minutes, + testAccountId, + "deleteMultiRegionAccessPoint", + ) + } + + private suspend fun waitUntilMultiRegionAccessPointOperationCompletes( + s3ControlClient: S3ControlClient, + request: String, + timeoutAfter: Duration, + testAccountId: String, + operation: String, + ) { + withTimeout(timeoutAfter) { + var status: String? = null + while (true) { + val latestStatus = s3ControlClient.describeMultiRegionAccessPointOperation { + accountId = testAccountId + requestTokenArn = request + }.asyncOperation?.requestStatus + + when (latestStatus) { + "SUCCEEDED" -> { + println("$operation operation succeeded.") + return@withTimeout + } + "FAILED" -> throw IllegalStateException("$operation operation failed") + else -> { + if (status == null || latestStatus != status) { + println("Waiting on $operation operation. Status: $latestStatus ") + status = latestStatus + } + } + } + + delay(10.seconds) // Avoid constant status checks + } + } + } + + internal suspend fun multiRegionAccessPointWasCreated( + s3Control: S3ControlClient, + multiRegionAccessPointName: String, + testAccountId: String, + ): Boolean { + println("Checking if multi region access point was created: $multiRegionAccessPointName") + + return s3Control.listMultiRegionAccessPoints { + accountId = testAccountId + }.accessPoints?.any { it.name == multiRegionAccessPointName } ?: false + } }