Unverified Commit e559e4cf authored by Russell Cohen's avatar Russell Cohen Committed by GitHub
Browse files

Add Route53 Hosted Zone Trimming (#999)

* Add Route53 Hosted Zone Trimming

Route53 requires a customization where `/hostedzone/` is stripped from hosted zone arguments when round-tripped. This adds a trait and customization to the route53 model to trim hosted zone prefixes.

* Add missing copyright header

* Also allow no leading slash

* Add route53 to smoke test because it has unit tests

* Update changelog

* Add missing call to flush()

* Remove unused env_logger depedendency
parent 898cc93d
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -27,6 +27,12 @@ meta = { "breaking" = false, "tada" = false, "bug" = false }
references = ["smithy-rs#973"]
author = "rcoh"

[[aws-sdk-rust]]
message = "Add Route53 customization to trim `/hostedzone/` prefix prior to serialization. This fixes a bug where round-tripping a hosted zone id resulted in an error."
meta = { "breaking" = false, "tada" = false, "bug" = true }
references = ["smithy-rs#999", "smithy-rs#143", "aws-sdk-rust#344" ]
author = "rcoh"

[[aws-sdk-rust]]
message = "Fix bug where ECS credential provider could not perform retries."
meta = { "breaking" = false, "tada" = false, "bug" = true }
+61 −0
Original line number Diff line number Diff line
/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0.
 */

/// Strip the /hostedzone/ prefix from zone-id
pub fn trim_hosted_zone(zone: &mut Option<String>) {
    const PREFIXES: &[&str; 2] = &["/hostedzone/", "hostedzone/"];

    for prefix in PREFIXES {
        if let Some(core_zone) = zone.as_deref().unwrap_or_default().strip_prefix(prefix) {
            *zone = Some(core_zone.to_string());
            return;
        }
    }
}

#[cfg(test)]
mod test {
    use crate::hosted_zone_preprocessor::trim_hosted_zone;

    struct OperationInput {
        hosted_zone: Option<String>,
    }

    #[test]
    fn does_not_change_regular_zones() {
        let mut operation = OperationInput {
            hosted_zone: Some("Z0441723226OZ66S5ZCNZ".to_string()),
        };
        trim_hosted_zone(&mut operation.hosted_zone);
        assert_eq!(
            &operation.hosted_zone.unwrap_or_default(),
            "Z0441723226OZ66S5ZCNZ"
        );
    }

    #[test]
    fn sanitizes_prefixed_zone() {
        let mut operation = OperationInput {
            hosted_zone: Some("/hostedzone/Z0441723226OZ66S5ZCNZ".to_string()),
        };
        trim_hosted_zone(&mut operation.hosted_zone);
        assert_eq!(
            &operation.hosted_zone.unwrap_or_default(),
            "Z0441723226OZ66S5ZCNZ"
        );
    }

    #[test]
    fn allow_no_leading_slash() {
        let mut operation = OperationInput {
            hosted_zone: Some("hostedzone/Z0441723226OZ66S5ZCNZ".to_string()),
        };
        trim_hosted_zone(&mut operation.hosted_zone);
        assert_eq!(
            &operation.hosted_zone.unwrap_or_default(),
            "Z0441723226OZ66S5ZCNZ"
        );
    }
}
+3 −0
Original line number Diff line number Diff line
@@ -32,3 +32,6 @@ pub mod glacier_checksums;

/// Default middleware stack for AWS services
pub mod middleware;

/// Strip `hostedzone/` from hosted zone ids
pub mod hosted_zone_preprocessor;
+2 −0
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@ import software.amazon.smithy.rustsdk.customize.apigateway.ApiGatewayDecorator
import software.amazon.smithy.rustsdk.customize.auth.DisabledAuthDecorator
import software.amazon.smithy.rustsdk.customize.ec2.Ec2Decorator
import software.amazon.smithy.rustsdk.customize.glacier.GlacierDecorator
import software.amazon.smithy.rustsdk.customize.route53.Route53Decorator
import software.amazon.smithy.rustsdk.customize.s3.S3Decorator

val DECORATORS = listOf(
@@ -44,6 +45,7 @@ val DECORATORS = listOf(
    S3Decorator(),
    Ec2Decorator(),
    GlacierDecorator(),
    Route53Decorator(),

    // Only build docs-rs for linux to reduce load on docs.rs
    DocsRsMetadataDecorator(DocsRsMetadataSettings(targets = listOf("x86_64-unknown-linux-gnu"), allFeatures = true))
+80 −0
Original line number Diff line number Diff line
/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0.
 */
package software.amazon.smithy.rustsdk.customize.route53

import software.amazon.smithy.model.Model
import software.amazon.smithy.model.shapes.MemberShape
import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.model.shapes.ServiceShape
import software.amazon.smithy.model.shapes.Shape
import software.amazon.smithy.model.shapes.ShapeId
import software.amazon.smithy.model.traits.HttpLabelTrait
import software.amazon.smithy.model.transform.ModelTransformer
import software.amazon.smithy.rust.codegen.rustlang.Writable
import software.amazon.smithy.rust.codegen.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.rustlang.writable
import software.amazon.smithy.rust.codegen.smithy.CodegenContext
import software.amazon.smithy.rust.codegen.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.smithy.customize.OperationCustomization
import software.amazon.smithy.rust.codegen.smithy.customize.OperationSection
import software.amazon.smithy.rust.codegen.smithy.customize.RustCodegenDecorator
import software.amazon.smithy.rust.codegen.smithy.letIf
import software.amazon.smithy.rust.codegen.util.hasTrait
import software.amazon.smithy.rust.codegen.util.inputShape
import software.amazon.smithy.rustsdk.InlineAwsDependency
import java.util.logging.Logger

val Route53: ShapeId = ShapeId.from("com.amazonaws.route53#AWSDnsV20130401")

class Route53Decorator : RustCodegenDecorator {
    override val name: String = "Route53"
    override val order: Byte = 0
    private val logger: Logger = Logger.getLogger(javaClass.name)

    private fun applies(service: ServiceShape) = service.id == Route53
    override fun transformModel(service: ServiceShape, model: Model): Model {
        return model.letIf(applies(service)) {
            ModelTransformer.create().mapShapes(model) { shape ->
                shape.letIf(isHostId(shape)) {
                    logger.info("Adding TrimHostedZone trait to $shape")
                    (shape as MemberShape).toBuilder().addTrait(TrimHostedZone()).build()
                }
            }
        }
    }

    override fun operationCustomizations(
        codegenContext: CodegenContext,
        operation: OperationShape,
        baseCustomizations: List<OperationCustomization>
    ): List<OperationCustomization> {
        val hostedZoneMember = operation.inputShape(codegenContext.model).members().find { it.hasTrait<TrimHostedZone>() }
        return if (hostedZoneMember != null) {
            baseCustomizations + TrimHostedZoneCustomization(codegenContext.symbolProvider.toMemberName(hostedZoneMember))
        } else baseCustomizations
    }

    private fun isHostId(shape: Shape): Boolean {
        return (shape is MemberShape && shape.target == ShapeId.from("com.amazonaws.route53#ResourceId")) && shape.hasTrait<HttpLabelTrait>()
    }
}

class TrimHostedZoneCustomization(private val fieldName: String) : OperationCustomization() {
    override fun mutSelf(): Boolean = true
    override fun consumesSelf(): Boolean = true

    private val trimZone =
        RuntimeType.forInlineDependency(InlineAwsDependency.forRustFile("hosted_zone_preprocessor"))
            .member("trim_hosted_zone")

    override fun section(section: OperationSection): Writable {
        return when (section) {
            is OperationSection.MutateInput -> writable {
                rustTemplate("#{trim_hosted_zone}(&mut ${section.input}.$fieldName);", "trim_hosted_zone" to trimZone)
            }
            else -> emptySection
        }
    }
}
Loading