Repository: smithy-lang/smithy
Pull request #2446 (2024-11-07 23:20:26)

Inject tagging operation permissions for CFN
This commit updates the CFN resource schema generation to fill in the
permissions field of the tagging configuration for resources. The AwsTagIndex
is used to find the APIs and their relevant required actions to invoke.

This also refactors the Tagging updates into a single Mapper so the updates
aren't strewn about the general CfnConverter.

---
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.



Patch:
diff --git a/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/CfnConverter.java b/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/CfnConverter.java
index bf9988725..d04fe934c 100644
--- a/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/CfnConverter.java
+++ b/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/CfnConverter.java
@@ -25,16 +25,14 @@ import java.util.ServiceLoader;
 import java.util.Set;
 import software.amazon.smithy.aws.cloudformation.schema.CfnConfig;
 import software.amazon.smithy.aws.cloudformation.schema.CfnException;
+import software.amazon.smithy.aws.cloudformation.schema.fromsmithy.mappers.TaggingMapper;
 import software.amazon.smithy.aws.cloudformation.schema.model.Property;
 import software.amazon.smithy.aws.cloudformation.schema.model.ResourceSchema;
-import software.amazon.smithy.aws.cloudformation.schema.model.Tagging;
 import software.amazon.smithy.aws.cloudformation.traits.CfnNameTrait;
 import software.amazon.smithy.aws.cloudformation.traits.CfnResource;
 import software.amazon.smithy.aws.cloudformation.traits.CfnResourceIndex;
 import software.amazon.smithy.aws.cloudformation.traits.CfnResourceTrait;
 import software.amazon.smithy.aws.traits.ServiceTrait;
-import software.amazon.smithy.aws.traits.tagging.AwsTagIndex;
-import software.amazon.smithy.aws.traits.tagging.TaggableTrait;
 import software.amazon.smithy.jsonschema.JsonSchemaConverter;
 import software.amazon.smithy.jsonschema.JsonSchemaMapper;
 import software.amazon.smithy.jsonschema.PropertyNamingStrategy;
@@ -56,7 +54,6 @@ import software.amazon.smithy.utils.ListUtils;
 import software.amazon.smithy.utils.StringUtils;
 
 public final class CfnConverter {
-    private static final String DEFAULT_TAGS_NAME = "Tags";
     private ClassLoader classLoader = CfnConverter.class.getClassLoader();
     private CfnConfig config = new CfnConfig();
     private final List<Smithy2CfnExtension> extensions = new ArrayList<>();
@@ -305,20 +302,6 @@ public final class CfnConverter {
             builder.addDefinition(definitionName, definition.getValue());
         }
 
-        if (resourceShape.hasTrait(TaggableTrait.class)) {
-            AwsTagIndex tagsIndex = AwsTagIndex.of(environment.context.getModel());
-            TaggableTrait trait = resourceShape.expectTrait(TaggableTrait.class);
-            Tagging.Builder tagBuilder = Tagging.builder()
-                    .taggable(true)
-                    .tagOnCreate(tagsIndex.isResourceTagOnCreate(resourceShape.getId()))
-                    .tagProperty("/properties/" + getTagMemberName(resourceShape))
-                    .cloudFormationSystemTags(!trait.getDisableSystemTags())
-                    // Unless tag-on-create is supported, Smithy tagging means
-                    .tagUpdatable(true);
-
-            builder.tagging(tagBuilder.build());
-        }
-
         // Apply all the mappers' after methods.
         ResourceSchema resourceSchema = builder.build();
         for (CfnMapper mapper : environment.mappers) {
@@ -391,47 +374,8 @@ public final class CfnConverter {
             }
         });
 
-        injectTagsIfNecessary(builder, model, resource, cfnResource);
+        TaggingMapper.injectTagsMember(config, model, resource, builder);
 
         return builder.build();
     }
-
-    private String getTagMemberName(ResourceShape resource) {
-        return resource.getTrait(TaggableTrait.class)
-                .flatMap(TaggableTrait::getProperty)
-                .map(property -> {
-                    if (config.getDisableCapitalizedProperties()) {
-                        return property;
-                    }
-                    return StringUtils.capitalize(property);
-                })
-                .orElse(DEFAULT_TAGS_NAME);
-    }
-
-    private void injectTagsIfNecessary(
-        StructureShape.Builder builder,
-        Model model,
-        ResourceShape resource,
-        CfnResource cfnResource
-    ) {
-        String tagMemberName = getTagMemberName(resource);
-        if (resource.hasTrait(TaggableTrait.class)) {
-            AwsTagIndex tagIndex = AwsTagIndex.of(model);
-            TaggableTrait trait = resource.expectTrait(TaggableTrait.class);
-            if (!trait.getProperty().isPresent() || !cfnResource.getProperties()
-                    .containsKey(trait.getProperty().get())) {
-                if (trait.getProperty().isPresent()) {
-                    ShapeId definition = resource.getProperties().get(trait.getProperty().get());
-                    builder.addMember(tagMemberName, definition);
-                } else {
-                    // A valid TagResource operation certainly has a single tags input member.
-                    AwsTagIndex awsTagIndex = AwsTagIndex.of(model);
-                    Optional<ShapeId> tagOperation = tagIndex.getTagResourceOperation(resource.getId());
-                    MemberShape member = awsTagIndex.getTagsMember(tagOperation.get()).get();
-                    member = member.toBuilder().id(builder.getId().withMember(tagMemberName)).build();
-                    builder.addMember(member);
-                }
-            }
-        }
-    }
 }
diff --git a/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/mappers/CoreExtension.java b/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/mappers/CoreExtension.java
index 4b75d220d..38cadd973 100644
--- a/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/mappers/CoreExtension.java
+++ b/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/mappers/CoreExtension.java
@@ -34,6 +34,7 @@ public final class CoreExtension implements Smithy2CfnExtension {
                 new IdentifierMapper(),
                 new JsonAddMapper(),
                 new MutabilityMapper(),
-                new RequiredMapper());
+                new RequiredMapper(),
+                new TaggingMapper());
     }
 }
diff --git a/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/mappers/HandlerPermissionMapper.java b/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/mappers/HandlerPermissionMapper.java
index 9a449c98a..ba45cbbb7 100644
--- a/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/mappers/HandlerPermissionMapper.java
+++ b/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/mappers/HandlerPermissionMapper.java
@@ -40,7 +40,7 @@ import software.amazon.smithy.utils.SmithyInternalApi;
  * @see <a href="https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-schema.html#schema-properties-handlers">handlers Docs</a>
  */
 @SmithyInternalApi
-public final class HandlerPermissionMapper implements CfnMapper {
+final class HandlerPermissionMapper implements CfnMapper {
     @Override
     public void before(Context context, ResourceSchema.Builder resourceSchema) {
         if (context.getConfig().getDisableHandlerPermissionGeneration()) {
@@ -97,7 +97,7 @@ public final class HandlerPermissionMapper implements CfnMapper {
                         .permissions(permissions).build()));
     }
 
-    private Set<String> getPermissionsEntriesForOperation(Model model, ServiceShape service, ShapeId operationId) {
+    static Set<String> getPermissionsEntriesForOperation(Model model, ServiceShape service, ShapeId operationId) {
         OperationShape operation = model.expectShape(operationId, OperationShape.class);
         Set<String> permissionsEntries = new TreeSet<>();
 
diff --git a/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/mappers/TaggingMapper.java b/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/mappers/TaggingMapper.java
new file mode 100644
index 000000000..3ffad4609
--- /dev/null
+++ b/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/mappers/TaggingMapper.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.aws.cloudformation.schema.fromsmithy.mappers;
+
+import static software.amazon.smithy.aws.cloudformation.schema.fromsmithy.mappers.HandlerPermissionMapper.getPermissionsEntriesForOperation;
+
+import java.util.Optional;
+import software.amazon.smithy.aws.cloudformation.schema.CfnConfig;
+import software.amazon.smithy.aws.cloudformation.schema.fromsmithy.CfnMapper;
+import software.amazon.smithy.aws.cloudformation.schema.fromsmithy.Context;
+import software.amazon.smithy.aws.cloudformation.schema.model.ResourceSchema;
+import software.amazon.smithy.aws.cloudformation.schema.model.Tagging;
+import software.amazon.smithy.aws.cloudformation.traits.CfnResource;
+import software.amazon.smithy.aws.cloudformation.traits.CfnResourceIndex;
+import software.amazon.smithy.aws.traits.tagging.AwsTagIndex;
+import software.amazon.smithy.aws.traits.tagging.TaggableTrait;
+import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.shapes.MemberShape;
+import software.amazon.smithy.model.shapes.ResourceShape;
+import software.amazon.smithy.model.shapes.ServiceShape;
+import software.amazon.smithy.model.shapes.ShapeId;
+import software.amazon.smithy.model.shapes.StructureShape;
+import software.amazon.smithy.utils.SmithyInternalApi;
+import software.amazon.smithy.utils.StringUtils;
+
+/**
+ * Generates the resource's Tagging configuration based on the AwsTagIndex, including
+ * the tagging property and operations that interact with tags.
+ *
+ * @see <a href="https://github.com/aws-cloudformation/cloudformation-cli/blob/master/src/rpdk/core/data/schema/provider.definition.schema.v1.json#L198">permissions property definition</a>
+ */
+@SmithyInternalApi
+public final class TaggingMapper implements CfnMapper {
+    private static final String DEFAULT_TAGS_NAME = "Tags";
+
+    @SmithyInternalApi
+    public static void injectTagsMember(
+            CfnConfig config,
+            Model model,
+            ResourceShape resource,
+            StructureShape.Builder builder
+    ) {
+        String tagMemberName = getTagMemberName(config, resource);
+        if (resource.hasTrait(TaggableTrait.class)) {
+            AwsTagIndex tagIndex = AwsTagIndex.of(model);
+            TaggableTrait trait = resource.expectTrait(TaggableTrait.class);
+            CfnResourceIndex resourceIndex = CfnResourceIndex.of(model);
+            CfnResource cfnResource = resourceIndex.getResource(resource).get();
+
+            if (!trait.getProperty().isPresent() || !cfnResource.getProperties()
+                    .containsKey(trait.getProperty().get())) {
+                if (trait.getProperty().isPresent()) {
+                    ShapeId definition = resource.getProperties().get(trait.getProperty().get());
+                    builder.addMember(tagMemberName, definition);
+                } else {
+                    // A valid TagResource operation certainly has a single tags input member.
+                    Optional<ShapeId> tagOperation = tagIndex.getTagResourceOperation(resource.getId());
+                    MemberShape member = tagIndex.getTagsMember(tagOperation.get()).get();
+                    member = member.toBuilder().id(builder.getId().withMember(tagMemberName)).build();
+                    builder.addMember(member);
+                }
+            }
+        }
+    }
+
+    @Override
+    public ResourceSchema after(Context context, ResourceSchema resourceSchema) {
+        ResourceShape resourceShape = context.getResource();
+        if (!resourceShape.hasTrait(TaggableTrait.class)) {
+            return resourceSchema;
+        }
+
+        Model model = context.getModel();
+        ServiceShape service = context.getService();
+        AwsTagIndex tagsIndex = AwsTagIndex.of(model);
+        TaggableTrait trait = resourceShape.expectTrait(TaggableTrait.class);
+        Tagging.Builder tagBuilder = Tagging.builder()
+                .taggable(true)
+                .tagOnCreate(tagsIndex.isResourceTagOnCreate(resourceShape.getId()))
+                .tagProperty("/properties/" + getTagMemberName(context.getConfig(), resourceShape))
+                .cloudFormationSystemTags(!trait.getDisableSystemTags())
+                // Unless tag-on-create is supported, Smithy tagging means
+                .tagUpdatable(true);
+
+        // Add the tagging permissions based on the defined tagging operations.
+        tagsIndex.getTagResourceOperation(resourceShape)
+                .map(operation -> getPermissionsEntriesForOperation(model, service, operation))
+                .ifPresent(tagBuilder::addPermissions);
+        tagsIndex.getListTagsForResourceOperation(resourceShape)
+                .map(operation -> getPermissionsEntriesForOperation(model, service, operation))
+                .ifPresent(tagBuilder::addPermissions);
+        tagsIndex.getUntagResourceOperation(resourceShape)
+                .map(operation -> getPermissionsEntriesForOperation(model, service, operation))
+                .ifPresent(tagBuilder::addPermissions);
+
+        return resourceSchema.toBuilder().tagging(tagBuilder.build()).build();
+    }
+
+    private static String getTagMemberName(CfnConfig config, ResourceShape resource) {
+        return resource.getTrait(TaggableTrait.class)
+                .flatMap(TaggableTrait::getProperty)
+                .map(property -> {
+                    if (config.getDisableCapitalizedProperties()) {
+                        return property;
+                    }
+                    return StringUtils.capitalize(property);
+                })
+                .orElse(DEFAULT_TAGS_NAME);
+    }
+}
diff --git a/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/model/Tagging.java b/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/model/Tagging.java
index cd429ca79..4a3073044 100644
--- a/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/model/Tagging.java
+++ b/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/model/Tagging.java
@@ -15,6 +15,10 @@
 
 package software.amazon.smithy.aws.cloudformation.schema.model;
 
+import java.util.Collection;
+import java.util.Set;
+import java.util.TreeSet;
+import software.amazon.smithy.utils.SetUtils;
 import software.amazon.smithy.utils.SmithyBuilder;
 import software.amazon.smithy.utils.ToSmithyBuilder;
 
@@ -27,6 +31,7 @@ public final class Tagging implements ToSmithyBuilder<Tagging> {
     private final boolean tagUpdatable;
     private final String tagProperty;
     private final boolean cloudFormationSystemTags;
+    private final Set<String> permissions;
 
     private Tagging(Builder builder) {
         taggable = builder.taggable;
@@ -34,6 +39,7 @@ public final class Tagging implements ToSmithyBuilder<Tagging> {
         tagUpdatable = builder.tagUpdatable;
         cloudFormationSystemTags = builder.cloudFormationSystemTags;
         tagProperty = builder.tagProperty;
+        this.permissions = SetUtils.orderedCopyOf(builder.permissions);
     }
 
     public static Builder builder() {
@@ -85,6 +91,15 @@ public final class Tagging implements ToSmithyBuilder<Tagging> {
         return tagProperty;
     }
 
+    /**
+     * Returns the set of permissions required to interact with this resource's tags.
+     *
+     * @return the set of permissions.
+     */
+    public Set<String> getPermissions() {
+        return permissions;
+    }
+
     @Override
     public Builder toBuilder() {
         return builder()
@@ -92,7 +107,8 @@ public final class Tagging implements ToSmithyBuilder<Tagging> {
                 .tagOnCreate(tagOnCreate)
                 .tagUpdatable(tagUpdatable)
                 .cloudFormationSystemTags(cloudFormationSystemTags)
-                .tagProperty(tagProperty);
+                .tagProperty(tagProperty)
+                .permissions(permissions);
     }
 
     public static final class Builder implements SmithyBuilder<Tagging> {
@@ -101,6 +117,7 @@ public final class Tagging implements ToSmithyBuilder<Tagging> {
         private boolean tagUpdatable;
         private boolean cloudFormationSystemTags;
         private String tagProperty;
+        private final Set<String> permissions = new TreeSet<>();
 
         @Override
         public Tagging build() {
@@ -131,5 +148,28 @@ public final class Tagging implements ToSmithyBuilder<Tagging> {
             this.tagProperty = tagProperty;
             return this;
         }
+
+        public Builder permissions(Collection<String> permissions) {
+            this.permissions.clear();
+            this.permissions.addAll(permissions);
+            return this;
+        }
+
+        public Builder addPermissions(Collection<String> permissions) {
+            for (String permission : permissions) {
+                addPermission(permission);
+            }
+            return this;
+        }
+
+        public Builder addPermission(String permission) {
+            this.permissions.add(permission);
+            return this;
+        }
+
+        public Builder clearPermissions() {
+            this.permissions.clear();
+            return this;
+        }
     }
 }
diff --git a/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/tagging/TaggableResourceValidator.java b/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/tagging/TaggableResourceValidator.java
index 417bc1627..2dfa663ad 100644
--- a/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/tagging/TaggableResourceValidator.java
+++ b/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/tagging/TaggableResourceValidator.java
@@ -43,14 +43,23 @@ public final class TaggableResourceValidator extends AbstractValidator {
         List<ValidationEvent> events = new LinkedList<>();
         TopDownIndex topDownIndex = TopDownIndex.of(model);
         AwsTagIndex tagIndex = AwsTagIndex.of(model);
-        for (ServiceShape service : model.getServiceShapesWithTrait(TagEnabledTrait.class)) {
+        for (ServiceShape service : model.getServiceShapes()) {
             for (ResourceShape resource : topDownIndex.getContainedResources(service)) {
+                boolean resourceLikelyTaggable = false;
                 if (resource.hasTrait(TaggableTrait.class)) {
                     events.addAll(validateResource(model, resource, service, tagIndex));
-                } else if (resource.hasTrait(ArnTrait.class) && tagIndex.serviceHasTagApis(service.getId())) {
+                    resourceLikelyTaggable = true;
+                } else if (resource.hasTrait(ArnTrait.class) && tagIndex.serviceHasTagApis(service)) {
                     // If a resource does not have the taggable trait, but has an ARN, and the service has tag
                     // operations, it is most likely a mistake.
                     events.add(warning(resource, "Resource is likely missing `aws.api#taggable` trait."));
+                    resourceLikelyTaggable = true;
+                }
+
+                // It's possible the resource was marked as taggable but the service isn't tagEnabled.
+                if (resourceLikelyTaggable && !service.hasTrait(TagEnabledTrait.class)) {
+                    events.add(warning(service, "Service has resources with `aws.api#taggable` applied but does not "
+                            + "have the `aws.api#tagEnabled` trait."));
                 }
             }
         }


Test patch:
diff --git a/smithy-aws-cloudformation/src/test/resources/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/provider.definition.schema.v1.json b/smithy-aws-cloudformation/src/test/resources/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/provider.definition.schema.v1.json
index 5a4d0ac45..6130be671 100644
--- a/smithy-aws-cloudformation/src/test/resources/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/provider.definition.schema.v1.json
+++ b/smithy-aws-cloudformation/src/test/resources/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/provider.definition.schema.v1.json
@@ -145,6 +145,13 @@
                     "description": "A reference to the Tags property in the schema.",
                     "$ref": "http://json-schema.org/draft-07/schema#/properties/$ref",
                     "default": "/properties/Tags"
+                },
+                "permissions": {
+                    "type": "array",
+                    "items": {
+                        "type": "string"
+                    },
+                    "additionalItems": false
                 }
             },
             "required": [
diff --git a/smithy-aws-cloudformation/src/test/resources/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/weather-service-wide.cfn.json b/smithy-aws-cloudformation/src/test/resources/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/weather-service-wide.cfn.json
index 7a9311de5..77455953b 100644
--- a/smithy-aws-cloudformation/src/test/resources/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/weather-service-wide.cfn.json
+++ b/smithy-aws-cloudformation/src/test/resources/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/weather-service-wide.cfn.json
@@ -83,7 +83,12 @@
         "tagProperty": "/properties/Tags",
         "tagUpdatable": true,
         "cloudFormationSystemTags": true,
-        "taggable": true
+        "taggable": true,
+        "permissions": [
+            "weather:ListTagsForResource",
+            "weather:TagResource",
+            "weather:UntagResource"
+        ]
     },
     "additionalProperties": false
 }
diff --git a/smithy-aws-cloudformation/src/test/resources/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/weather.cfn.json b/smithy-aws-cloudformation/src/test/resources/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/weather.cfn.json
index 7dd4df090..782a98e8a 100644
--- a/smithy-aws-cloudformation/src/test/resources/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/weather.cfn.json
+++ b/smithy-aws-cloudformation/src/test/resources/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/weather.cfn.json
@@ -82,7 +82,12 @@
         "tagProperty": "/properties/Tags",
         "tagUpdatable": true,
         "cloudFormationSystemTags": true,
-        "taggable": true
+        "taggable": true,
+        "permissions": [
+            "weather:ListTagsForCity",
+            "weather:TagCity",
+            "weather:UntagCity"
+        ]
     },
     "additionalProperties": false
 }
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/tagging/tagging-warnings.errors b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/tagging/tagging-warnings.errors
index eb9615077..494564873 100644
--- a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/tagging/tagging-warnings.errors
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/tagging/tagging-warnings.errors
@@ -3,3 +3,4 @@
 [WARNING] example.weather#Weather: Service marked `aws.api#TagEnabled` is missing an operation named 'UntagResource.' | ServiceTagging
 [WARNING] example.weather#City: Suggested tag property name is '[T|t]ags'. | TagResourcePropertyName
 [DANGER] example.weather#UpdateCity: Update and put resource lifecycle operations should not support updating tags because it is a privileged operation that modifies access. | TaggableResource
+[WARNING] example.weather#Weather2: Service has resources with `aws.api#taggable` applied but does not have the `aws.api#tagEnabled` trait. | TaggableResource
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/tagging/tagging-warnings.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/tagging/tagging-warnings.smithy
index b15aeecee..420f0c588 100644
--- a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/tagging/tagging-warnings.smithy
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/tagging/tagging-warnings.smithy
@@ -19,6 +19,11 @@ service Weather {
     operations: [GetCurrentTime]
 }
 
+service Weather2 {
+    version: "2006-03-01",
+    resources: [City]
+}
+
 structure Tag {
     key: String
     value: String
diff --git a/smithy-protocol-tests/model/rpcv2Cbor/defaults.smithy b/smithy-protocol-tests/model/rpcv2Cbor/defaults.smithy
index f2d969bca..53df9a2c3 100644
--- a/smithy-protocol-tests/model/rpcv2Cbor/defaults.smithy
+++ b/smithy-protocol-tests/model/rpcv2Cbor/defaults.smithy
@@ -25,7 +25,7 @@ apply OperationWithDefaults @httpRequestTests([
             "Content-Length"
         ],
         bodyMediaType: "application/cbor"
-        body: "v2hkZWZhdWx0c79tZGVmYXVsdFN0cmluZ2JoaW5kZWZhdWx0Qm9vbGVhbvVrZGVmYXVsdExpc3Sf/3BkZWZhdWx0VGltZXN0YW1wwQBrZGVmYXVsdEJsb2JjYWJja2RlZmF1bHRCeXRlAWxkZWZhdWx0U2hvcnQBbmRlZmF1bHRJbnRlZ2VyCmtkZWZhdWx0TG9uZxhkbGRlZmF1bHRGbG9hdPo/gAAAbWRlZmF1bHREb3VibGX6P4AAAGpkZWZhdWx0TWFwv/9rZGVmYXVsdEVudW1jRk9PbmRlZmF1bHRJbnRFbnVtAWtlbXB0eVN0cmluZ2BsZmFsc2VCb29sZWFu9GllbXB0eUJsb2JgaHplcm9CeXRlAGl6ZXJvU2hvcnQAa3plcm9JbnRlZ2VyAGh6ZXJvTG9uZwBpemVyb0Zsb2F0+gAAAABqemVyb0RvdWJsZfoAAAAA//8="
+        body: "v2hkZWZhdWx0c79tZGVmYXVsdFN0cmluZ2JoaW5kZWZhdWx0Qm9vbGVhbvVrZGVmYXVsdExpc3Sf/3BkZWZhdWx0VGltZXN0YW1wwQBrZGVmYXVsdEJsb2JDYWJja2RlZmF1bHRCeXRlAWxkZWZhdWx0U2hvcnQBbmRlZmF1bHRJbnRlZ2VyCmtkZWZhdWx0TG9uZxhkbGRlZmF1bHRGbG9hdPo/gAAAbWRlZmF1bHREb3VibGX6P4AAAGpkZWZhdWx0TWFwv/9rZGVmYXVsdEVudW1jRk9PbmRlZmF1bHRJbnRFbnVtAWtlbXB0eVN0cmluZ2BsZmFsc2VCb29sZWFu9GllbXB0eUJsb2JAaHplcm9CeXRlAGl6ZXJvU2hvcnQAa3plcm9JbnRlZ2VyAGh6ZXJvTG9uZwBpemVyb0Zsb2F0+gAAAABqemVyb0RvdWJsZfoAAAAA//8"
         params: {
             defaults: {}
         }