Repository: cloudtools/troposphere
Pull request #1936 (2021-07-18 06:24:15)

AWS::Logs::ResourcePolicy


Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 8bb28a4a..95ea2994 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,3 +1,26 @@
+3.0.2 (2021*07*24)
+------------------
+* Add JWT to apigatewayv2 valid_authorizer_types (#1929)
+* [batch] Update ContainerProperties properties (#1930)
+* Remove p3s directory
+* Update ImageBuilder per 2021-07-01 changes
+* Update ServiceDiscovery per 2021-07-08 changes
+* Update CodeDeploy per 2021-07-08 changes
+* Add KmsKeyId Attribute to LogGroup (#1931)
+* Added missing AWS::Neptune::DBCluster properties (#1932)
+* Added Sign and Verify key usage (#1935)
+* Fix CanarySettings PercentTraffic definition
+* Fix NetworkFirewall properties
+* Fixup formatting in NetworkFirewall
+* Use jsonpatch to fixup spec files before generating code
+* Update DataBrew per 2021-07-09 changes
+* Update Logs per 2021-07-15 changes
+* Update EC2 per 2021-07-21 changes
+* Update Cassandra per 2021-07-21 changes
+* Add AWS::LookoutEquipment per 2021-07-22 changes
+* Update QLDB per 2021-07-22 changes
+* Update CloudWatch per 2021-07-22 changes
+
 3.0.1 (2021*07*06)
 ------------------
 * Fix CHANGELOG with correct 3.0.0 release date
diff --git a/README.rst b/README.rst
index f0f3a7a0..4c9a13c1 100644
--- a/README.rst
+++ b/README.rst
@@ -250,6 +250,7 @@ Currently supported AWS resource types
 - `AWS::LicenseManager`_
 - `AWS::Location`_
 - `AWS::Logs`_
+- `AWS::LookoutEquipment`_
 - `AWS::LookoutMetrics`_
 - `AWS::LookoutVision`_
 - `AWS::Macie`_
@@ -278,6 +279,8 @@ Currently supported AWS resource types
 - `AWS::RoboMaker`_
 - `AWS::Route53`_
 - `AWS::Route53Resolver`_
+- `AWS::Route53RecoveryControl`_
+- `AWS::Route53RecoveryReadiness`_
 - `AWS::S3`_
 - `AWS::S3ObjectLambda`_
 - `AWS::S3Outposts`_
@@ -296,6 +299,7 @@ Currently supported AWS resource types
 - `AWS::ServiceCatalog`_
 - `AWS::ServiceCatalogAppRegistry`_
 - `AWS::ServiceDiscovery`_
+- `AWS::Signer`_
 - `AWS::StepFunctions`_
 - `AWS::Synthetics`_
 - `AWS::Timestream`_
@@ -528,6 +532,7 @@ See `LICENSE`_ for the troposphere full license text.
 .. _`AWS::LicenseManager`: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_LicenseManager.html
 .. _`AWS::Location`: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_Location.html
 .. _`AWS::Logs`: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_Logs.html
+.. _`AWS::LookoutEquipment`: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_LookoutEquipment.html
 .. _`AWS::LookoutMetrics`: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_LookoutMetrics.html
 .. _`AWS::LookoutVision`: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_LookoutVision.html
 .. _`AWS::Macie`: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_Macie.html
@@ -556,6 +561,8 @@ See `LICENSE`_ for the troposphere full license text.
 .. _`AWS::RoboMaker`: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_RoboMaker.html
 .. _`AWS::Route53`: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_Route53.html
 .. _`AWS::Route53Resolver`: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_Route53Resolver.html
+.. _`AWS::Route53RecoveryControl`: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_Route53RecoveryControl.html
+.. _`AWS::Route53RecoveryReadiness`: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_Route53RecoveryReadiness.html
 .. _`AWS::S3`: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_S3.html
 .. _`AWS::S3ObjectLambda`: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_S3ObjectLambda.html
 .. _`AWS::S3Outposts`: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_S3Outposts.html
@@ -574,6 +581,7 @@ See `LICENSE`_ for the troposphere full license text.
 .. _`AWS::ServiceCatalog`: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_ServiceCatalog.html
 .. _`AWS::ServiceCatalogAppRegistry`: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_ServiceCatalogAppRegistry.html
 .. _`AWS::ServiceDiscovery`: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_ServiceDiscovery.html
+.. _`AWS::Signer`: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_Signer.html
 .. _`AWS::StepFunctions`: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_StepFunctions.html
 .. _`AWS::Synthetics`: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_Synthetics.html
 .. _`AWS::Timestream`: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_Timestream.html
diff --git a/docs/conf.py b/docs/conf.py
index fa67c905..cf658a0a 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -25,7 +25,7 @@ author = 'cloudtools'
 # The short X.Y version
 version = ''
 # The full version, including alpha/beta/rc tags
-release = '3.0.1'
+release = '3.0.2'
 
 
 # -- General configuration ---------------------------------------------------
diff --git a/examples/RedshiftClusterInVpc.py b/examples/RedshiftClusterInVpc.py
index 0ce2a8df..7b49e7bd 100644
--- a/examples/RedshiftClusterInVpc.py
+++ b/examples/RedshiftClusterInVpc.py
@@ -106,7 +106,7 @@ redshiftcluster = t.add_resource(
         MasterUsername=Ref("MasterUsername"),
         MasterUserPassword=Ref("MasterUserPassword"),
         ClusterParameterGroupName=Ref("RedshiftClusterParameterGroup"),
-        VpcSecurityGroupIds=Ref("SecurityGroup"),
+        VpcSecurityGroupIds=[Ref("SecurityGroup")],
         ClusterSubnetGroupName=Ref("RedshiftClusterSubnetGroup"),
     )
 )
@@ -130,7 +130,7 @@ redshiftclustersubnetgroup = t.add_resource(
     ClusterSubnetGroup(
         "RedshiftClusterSubnetGroup",
         Description="Cluster subnet group",
-        SubnetIds=Ref("Subnet"),
+        SubnetIds=[Ref("Subnet")],
     )
 )
 
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 042cf18e..f0a925d4 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -5,6 +5,9 @@
 cfn_flip>=1.0.2
 awacs>=2.0.0
 
+# ---- Use jsonpatch to fixup spec files
+jsonpatch
+
 # ---- Documentation Libraries
 Sphinx>=1.8
 sphinx_rtd_theme>=0.4.2
diff --git a/scripts/gen.py b/scripts/gen.py
index eaa3cae7..50612c96 100644
--- a/scripts/gen.py
+++ b/scripts/gen.py
@@ -3,8 +3,10 @@
 
 import argparse
 import json
+import os
 import sys
 
+import jsonpatch
 import yaml
 
 # Python code generator to create new troposphere classes from the
@@ -516,6 +518,13 @@ if __name__ == "__main__":
     f = open(args.filename[0])
     j = json.load(f)
 
+    # Apply json patches
+    patch_dir = "scripts/patches"
+    for patch_file in os.listdir(patch_dir):
+        if patch_file.endswith(".json"):
+            patch = json.loads(open(os.path.join(patch_dir, patch_file)).read())
+            j = jsonpatch.apply_patch(j, patch)
+
     spec_version = j["ResourceSpecificationVersion"]
 
     r = Resources()
diff --git a/scripts/patches/DataBrew.json b/scripts/patches/DataBrew.json
new file mode 100644
index 00000000..12cd2281
--- /dev/null
+++ b/scripts/patches/DataBrew.json
@@ -0,0 +1,1 @@
+[{"op": "add", "path": "/PropertyTypes/AWS::DataBrew::Recipe.Action/Properties/Parameters/Type", "value": "RecipeParameters"}]
diff --git a/scripts/patches/IoTSiteWise.json b/scripts/patches/IoTSiteWise.json
new file mode 100644
index 00000000..7b6b8e38
--- /dev/null
+++ b/scripts/patches/IoTSiteWise.json
@@ -0,0 +1,4 @@
+[
+{"op": "move", "from": "/PropertyTypes/AWS::IoTSiteWise::AccessPolicy.Portal", "path": "/PropertyTypes/AWS::IoTSiteWise::AccessPolicy.PortalProperty"},
+{"op": "replace", "path": "/PropertyTypes/AWS::IoTSiteWise::AccessPolicy.AccessPolicyResource/Properties/Portal/Type", "value": "PortalProperty"}
+]
diff --git a/scripts/patches/NetworkFirewall.json b/scripts/patches/NetworkFirewall.json
new file mode 100644
index 00000000..8f15ea89
--- /dev/null
+++ b/scripts/patches/NetworkFirewall.json
@@ -0,0 +1,6 @@
+[
+{"op": "move", "from": "/PropertyTypes/AWS::NetworkFirewall::RuleGroup.RuleGroup", "path": "/PropertyTypes/AWS::NetworkFirewall::RuleGroup.RuleGroupProperty"},
+{"op": "replace", "path": "/ResourceTypes/AWS::NetworkFirewall::RuleGroup/Properties/RuleGroup/Type", "value": "RuleGroupProperty"},
+{"op": "move", "from": "/PropertyTypes/AWS::NetworkFirewall::FirewallPolicy.FirewallPolicy", "path": "/PropertyTypes/AWS::NetworkFirewall::FirewallPolicy.FirewallPolicyProperty"},
+{"op": "replace", "path": "/ResourceTypes/AWS::NetworkFirewall::FirewallPolicy/Properties/FirewallPolicy/Type", "value": "FirewallPolicyProperty"}
+]
diff --git a/troposphere/__init__.py b/troposphere/__init__.py
index b1a16274..a0ee5fed 100644
--- a/troposphere/__init__.py
+++ b/troposphere/__init__.py
@@ -12,7 +12,7 @@ import cfn_flip
 
 from . import validators
 
-__version__ = "3.0.1"
+__version__ = "3.0.2"
 
 # constants for DeletionPolicy and UpdateReplacePolicy
 Delete = "Delete"
diff --git a/troposphere/apigateway.py b/troposphere/apigateway.py
index 5d27f1d2..a569e746 100644
--- a/troposphere/apigateway.py
+++ b/troposphere/apigateway.py
@@ -78,7 +78,7 @@ class CanarySetting(AWSProperty):
 
     props = {
         "DeploymentId": (str, False),
-        "PercentTraffic": ([double], False),
+        "PercentTraffic": (double, False),
         "StageVariableOverrides": (dict, False),
         "UseStageCache": (boolean, False),
     }
@@ -103,7 +103,7 @@ class ClientCertificate(AWSObject):
 class DeploymentCanarySettings(AWSProperty):
 
     props = {
-        "PercentTraffic": ([double], False),
+        "PercentTraffic": (double, False),
         "StageVariableOverrides": (dict, False),
         "UseStageCache": (boolean, False),
     }
@@ -217,6 +217,7 @@ class DomainName(AWSObject):
         "DomainName": (str, True),
         "EndpointConfiguration": (EndpointConfiguration, False),
         "MutualTlsAuthentication": (MutualTlsAuthentication, False),
+        "OwnershipVerificationCertificateArn": (str, False),
         "RegionalCertificateArn": (str, False),
         "SecurityPolicy": (str, False),
         "Tags": (Tags, False),
diff --git a/troposphere/apigatewayv2.py b/troposphere/apigatewayv2.py
index 7d755de9..e85066e7 100644
--- a/troposphere/apigatewayv2.py
+++ b/troposphere/apigatewayv2.py
@@ -170,6 +170,8 @@ class DomainNameConfiguration(AWSProperty):
         "CertificateArn": (str, False),
         "CertificateName": (str, False),
         "EndpointType": (str, False),
+        "OwnershipVerificationCertificateArn": (str, False),
+        "SecurityPolicy": (str, False),
     }
 
 
diff --git a/troposphere/appsync.py b/troposphere/appsync.py
index 319bb7cd..6e65b90a 100644
--- a/troposphere/appsync.py
+++ b/troposphere/appsync.py
@@ -168,6 +168,14 @@ class AdditionalAuthenticationProvider(AWSProperty):
     }
 
 
+class LambdaAuthorizerConfig(AWSProperty):
+    props = {
+        "AuthorizerResultTtlInSeconds": (double, False),
+        "AuthorizerUri": (str, False),
+        "IdentityValidationExpression": (str, False),
+    }
+
+
 class LogConfig(AWSProperty):
     props = {
         "CloudWatchLogsRoleArn": (str, False),
@@ -194,6 +202,7 @@ class GraphQLApi(AWSObject):
             False,
         ),
         "AuthenticationType": (str, True),
+        "LambdaAuthorizerConfig": (LambdaAuthorizerConfig, False),
         "LogConfig": (LogConfig, False),
         "Name": (str, True),
         "OpenIDConnectConfig": (OpenIDConnectConfig, False),
diff --git a/troposphere/athena.py b/troposphere/athena.py
index e28398d2..f2cd58e9 100644
--- a/troposphere/athena.py
+++ b/troposphere/athena.py
@@ -59,6 +59,17 @@ class NamedQuery(AWSObject):
     }
 
 
+class PreparedStatement(AWSObject):
+    resource_type = "AWS::Athena::PreparedStatement"
+
+    props = {
+        "Description": (str, False),
+        "QueryStatement": (str, True),
+        "StatementName": (str, True),
+        "WorkGroup": (str, True),
+    }
+
+
 class EngineVersion(AWSProperty):
     props = {
         "EffectiveEngineVersion": (str, False),
diff --git a/troposphere/autoscaling.py b/troposphere/autoscaling.py
index a2ad4efc..433e6a92 100644
--- a/troposphere/autoscaling.py
+++ b/troposphere/autoscaling.py
@@ -4,7 +4,7 @@
 # See LICENSE file for full license.
 
 from . import AWSHelperFn, AWSObject, AWSProperty, FindInMap, If, Ref, cloudformation
-from .validators import boolean, exactly_one, integer, mutually_exclusive
+from .validators import boolean, double, exactly_one, integer, mutually_exclusive
 
 EC2_INSTANCE_LAUNCH = "autoscaling:EC2_INSTANCE_LAUNCH"
 EC2_INSTANCE_LAUNCH_ERROR = "autoscaling:EC2_INSTANCE_LAUNCH_ERROR"
@@ -148,6 +148,7 @@ class InstancesDistribution(AWSProperty):
 class LaunchTemplateOverrides(AWSProperty):
     props = {
         "InstanceType": (str, False),
+        "LaunchTemplateSpecification": (LaunchTemplateSpecification, False),
         "WeightedCapacity": (str, False),
     }
 
@@ -274,6 +275,55 @@ class LaunchConfiguration(AWSObject):
     }
 
 
+class PredictiveScalingPredefinedLoadMetric(AWSProperty):
+    props = {
+        "PredefinedMetricType": (str, True),
+        "ResourceLabel": (str, False),
+    }
+
+
+class PredictiveScalingPredefinedMetricPair(AWSProperty):
+    props = {
+        "PredefinedMetricType": (str, True),
+        "ResourceLabel": (str, False),
+    }
+
+
+class PredictiveScalingPredefinedScalingMetric(AWSProperty):
+    props = {
+        "PredefinedMetricType": (str, True),
+        "ResourceLabel": (str, False),
+    }
+
+
+class PredictiveScalingMetricSpecification(AWSProperty):
+    props = {
+        "PredefinedLoadMetricSpecification": (
+            PredictiveScalingPredefinedLoadMetric,
+            False,
+        ),
+        "PredefinedMetricPairSpecification": (
+            PredictiveScalingPredefinedMetricPair,
+            False,
+        ),
+        "PredefinedScalingMetricSpecification": (
+            PredictiveScalingPredefinedScalingMetric,
+            False,
+        ),
+        "TargetValue": (double, True),
+    }
+
+
+class PredictiveScalingConfiguration(AWSProperty):
+    props = {
+        "MaxCapacityBreachBehavior": (str, False),
+        "MaxCapacityBuffer": (integer, False),
+        "MetricSpecifications": ([PredictiveScalingMetricSpecification], True),
+        "Mode": (str, False),
+        "SchedulingBufferTime": (integer, False),
+    }
+
+
 class StepAdjustments(AWSProperty):
     props = {
         "MetricIntervalLowerBound": (integer, False),
@@ -326,6 +376,7 @@ class ScalingPolicy(AWSObject):
         "MetricAggregationType": (str, False),
         "MinAdjustmentMagnitude": (integer, False),
         "PolicyType": (str, False),
+        "PredictiveScalingConfiguration": (PredictiveScalingConfiguration, False),
         "ScalingAdjustment": (integer, False),
         "StepAdjustments": ([StepAdjustments], False),
         "TargetTrackingConfiguration": (TargetTrackingConfiguration, False),
@@ -343,6 +394,7 @@ class ScheduledAction(AWSObject):
         "MinSize": (integer, False),
         "Recurrence": (str, False),
         "StartTime": (str, False),
+        "TimeZone": (str, False),
     }
 
 
diff --git a/troposphere/cassandra.py b/troposphere/cassandra.py
index 1e06ad3a..006d69e8 100644
--- a/troposphere/cassandra.py
+++ b/troposphere/cassandra.py
@@ -65,12 +65,20 @@ class BillingMode(AWSProperty):
     }
 
 
+class EncryptionSpecification(AWSProperty):
+    props = {
+        "EncryptionType": (str, True),
+        "KmsKeyIdentifier": (str, False),
+    }
+
+
 class Table(AWSObject):
     resource_type = "AWS::Cassandra::Table"
 
     props = {
         "BillingMode": (BillingMode, False),
         "ClusteringKeyColumns": ([ClusteringKeyColumn], False),
+        "EncryptionSpecification": (EncryptionSpecification, False),
         "KeyspaceName": (str, True),
         "PartitionKeyColumns": ([Column], True),
         "PointInTimeRecoveryEnabled": (boolean, False),
diff --git a/troposphere/cloudwatch.py b/troposphere/cloudwatch.py
index 8b869d22..892cb221 100644
--- a/troposphere/cloudwatch.py
+++ b/troposphere/cloudwatch.py
@@ -91,6 +91,7 @@ class MetricStat(AWSProperty):
 
 class MetricDataQuery(AWSProperty):
     props = {
+        "AccountId": (str, False),
         "Expression": (str, False),
         "Id": (str, True),
         "Label": (str, False),
diff --git a/troposphere/codebuild.py b/troposphere/codebuild.py
index fe366f10..700d59e5 100644
--- a/troposphere/codebuild.py
+++ b/troposphere/codebuild.py
@@ -366,6 +366,7 @@ class Project(AWSObject):
         "BadgeEnabled": (boolean, False),
         "BuildBatchConfig": (ProjectBuildBatchConfig, False),
         "Cache": (ProjectCache, False),
+        "ConcurrentBuildLimit": (integer, False),
         "Description": (str, False),
         "EncryptionKey": (str, False),
         "Environment": (Environment, True),
@@ -373,6 +374,7 @@ class Project(AWSObject):
         "LogsConfig": (LogsConfig, False),
         "Name": (str, False),
         "QueuedTimeoutInMinutes": (integer, False),
+        "ResourceAccessRole": (str, False),
         "SecondaryArtifacts": ([Artifacts], False),
         "SecondarySourceVersions": ([ProjectSourceVersion], False),
         "SecondarySources": ([Source], False),
@@ -382,6 +384,7 @@ class Project(AWSObject):
         "Tags": (Tags, False),
         "TimeoutInMinutes": (integer, False),
         "Triggers": (ProjectTriggers, False),
+        "Visibility": (str, False),
         "VpcConfig": (VpcConfig, False),
     }
 
diff --git a/troposphere/databrew.py b/troposphere/databrew.py
index 7bf17cdc..0d0091ae 100644
--- a/troposphere/databrew.py
+++ b/troposphere/databrew.py
@@ -4,7 +4,7 @@
 # See LICENSE file for full license.
 #
 # *** Do not modify - this file is autogenerated ***
-# Resource specification version: 35.0.0
+# Resource specification version: 39.8.0
 
 
 from troposphere import Tags
@@ -142,6 +142,45 @@ class Dataset(AWSObject):
     }
 
 
+class DatabaseTableOutputOptions(AWSProperty):
+    props = {
+        "TableName": (str, True),
+        "TempDirectory": (S3Location, False),
+    }
+
+
+class S3TableOutputOptions(AWSProperty):
+    props = {
+        "Location": (S3Location, True),
+    }
+
+
+class DataCatalogOutput(AWSProperty):
+    props = {
+        "CatalogId": (str, False),
+        "DatabaseName": (str, True),
+        "DatabaseOptions": (DatabaseTableOutputOptions, False),
+        "Overwrite": (boolean, False),
+        "S3Options": (S3TableOutputOptions, False),
+        "TableName": (str, True),
+    }
+
+
+class DatabaseOutput(AWSProperty):
+    props = {
+        "DatabaseOptions": (DatabaseTableOutputOptions, True),
+        "DatabaseOutputMode": (str, False),
+        "GlueConnectionName": (str, True),
+    }
+
+
+class JobSample(AWSProperty):
+    props = {
+        "Mode": (str, False),
+        "Size": (integer, False),
+    }
+
+
 class CsvOutputOptions(AWSProperty):
     props = {
         "Delimiter": (str, False),
@@ -165,20 +204,70 @@ class Output(AWSProperty):
     }
 
 
+class OutputLocation(AWSProperty):
+    props = {
+        "Bucket": (str, True),
+        "Key": (str, False),
+    }
+
+
+class ColumnSelector(AWSProperty):
+    props = {
+        "Name": (str, False),
+        "Regex": (str, False),
+    }
+
+
+class ParameterMap(AWSProperty):
+    props = {}
+
+
+class StatisticOverride(AWSProperty):
+    props = {
+        "Parameters": (ParameterMap, True),
+        "Statistic": (str, True),
+    }
+
+
+class StatisticsConfiguration(AWSProperty):
+    props = {
+        "IncludedStatistics": ([str], False),
+        "Overrides": ([StatisticOverride], False),
+    }
+
+
+class ColumnStatisticsConfiguration(AWSProperty):
+    props = {
+        "Selectors": ([ColumnSelector], False),
+        "Statistics": (StatisticsConfiguration, True),
+    }
+
+
+class ProfileConfiguration(AWSProperty):
+    props = {
+        "ColumnStatisticsConfigurations": ([ColumnStatisticsConfiguration], False),
+        "DatasetStatisticsConfiguration": (StatisticsConfiguration, False),
+        "ProfileColumns": ([ColumnSelector], False),
+    }
+
+
 class Job(AWSObject):
     resource_type = "AWS::DataBrew::Job"
 
     props = {
+        "DataCatalogOutputs": ([DataCatalogOutput], False),
+        "DatabaseOutputs": ([DatabaseOutput], False),
         "DatasetName": (str, False),
         "EncryptionKeyArn": (str, False),
         "EncryptionMode": (str, False),
-        "JobSample": (dict, False),
+        "JobSample": (JobSample, False),
         "LogSubscription": (str, False),
         "MaxCapacity": (integer, False),
         "MaxRetries": (integer, False),
         "Name": (str, True),
-        "OutputLocation": (dict, False),
+        "OutputLocation": (OutputLocation, False),
         "Outputs": ([Output], False),
+        "ProfileConfiguration": (ProfileConfiguration, False),
         "ProjectName": (str, False),
         "Recipe": (dict, False),
         "RoleArn": (str, True),
@@ -188,6 +277,13 @@ class Job(AWSObject):
     }
 
 
+class Sample(AWSProperty):
+    props = {
+        "Size": (integer, False),
+        "Type": (str, True),
+    }
+
+
 class Project(AWSObject):
     resource_type = "AWS::DataBrew::Project"
 
@@ -196,15 +292,128 @@ class Project(AWSObject):
         "Name": (str, True),
         "RecipeName": (str, True),
         "RoleArn": (str, True),
-        "Sample": (dict, False),
+        "Sample": (Sample, False),
         "Tags": (Tags, False),
     }
 
 
+class SecondaryInput(AWSProperty):
+    props = {
+        "DataCatalogInputDefinition": (DataCatalogInputDefinition, False),
+        "S3InputDefinition": (S3Location, False),
+    }
+
+
+class RecipeParameters(AWSProperty):
+    props = {
+        "AggregateFunction": (str, False),
+        "Base": (str, False),
+        "CaseStatement": (str, False),
+        "CategoryMap": (str, False),
+        "CharsToRemove": (str, False),
+        "CollapseConsecutiveWhitespace": (str, False),
+        "ColumnDataType": (str, False),
+        "ColumnRange": (str, False),
+        "Count": (str, False),
+        "CustomCharacters": (str, False),
+        "CustomStopWords": (str, False),
+        "CustomValue": (str, False),
+        "DatasetsColumns": (str, False),
+        "DateAddValue": (str, False),
+        "DateTimeFormat": (str, False),
+        "DateTimeParameters": (str, False),
+        "DeleteOtherRows": (str, False),
+        "Delimiter": (str, False),
+        "EndPattern": (str, False),
+        "EndPosition": (str, False),
+        "EndValue": (str, False),
+        "ExpandContractions": (str, False),
+        "Exponent": (str, False),
+        "FalseString": (str, False),
+        "GroupByAggFunctionOptions": (str, False),
+        "GroupByColumns": (str, False),
+        "HiddenColumns": (str, False),
+        "IgnoreCase": (str, False),
+        "IncludeInSplit": (str, False),
+        "Input": (dict, False),
+        "Interval": (str, False),
+        "IsText": (str, False),
+        "JoinKeys": (str, False),
+        "JoinType": (str, False),
+        "LeftColumns": (str, False),
+        "Limit": (str, False),
+        "LowerBound": (str, False),
+        "MapType": (str, False),
+        "ModeType": (str, False),
+        "MultiLine": (boolean, False),
+        "NumRows": (str, False),
+        "NumRowsAfter": (str, False),
+        "NumRowsBefore": (str, False),
+        "OrderByColumn": (str, False),
+        "OrderByColumns": (str, False),
+        "Other": (str, False),
+        "Pattern": (str, False),
+        "PatternOption1": (str, False),
+        "PatternOption2": (str, False),
+        "PatternOptions": (str, False),
+        "Period": (str, False),
+        "Position": (str, False),
+        "RemoveAllPunctuation": (str, False),
+        "RemoveAllQuotes": (str, False),
+        "RemoveAllWhitespace": (str, False),
+        "RemoveCustomCharacters": (str, False),
+        "RemoveCustomValue": (str, False),
+        "RemoveLeadingAndTrailingPunctuation": (str, False),
+        "RemoveLeadingAndTrailingQuotes": (str, False),
+        "RemoveLeadingAndTrailingWhitespace": (str, False),
+        "RemoveLetters": (str, False),
+        "RemoveNumbers": (str, False),
+        "RemoveSourceColumn": (str, False),
+        "RemoveSpecialCharacters": (str, False),
+        "RightColumns": (str, False),
+        "SampleSize": (str, False),
+        "SampleType": (str, False),
+        "SecondInput": (str, False),
+        "SecondaryInputs": ([SecondaryInput], False),
+        "SheetIndexes": ([integer], False),
+        "SheetNames": ([str], False),
+        "SourceColumn": (str, False),
+        "SourceColumn1": (str, False),
+        "SourceColumn2": (str, False),
+        "SourceColumns": (str, False),
+        "StartColumnIndex": (str, False),
+        "StartPattern": (str, False),
+        "StartPosition": (str, False),
+        "StartValue": (str, False),
+        "StemmingMode": (str, False),
+        "StepCount": (str, False),
+        "StepIndex": (str, False),
+        "StopWordsMode": (str, False),
+        "Strategy": (str, False),
+        "TargetColumn": (str, False),
+        "TargetColumnNames": (str, False),
+        "TargetDateFormat": (str, False),
+        "TargetIndex": (str, False),
+        "TimeZone": (str, False),
+        "TokenizerPattern": (str, False),
+        "TrueString": (str, False),
+        "UdfLang": (str, False),
+        "Units": (str, False),
+        "UnpivotColumn": (str, False),
+        "UpperBound": (str, False),
+        "UseNewDataFrame": (str, False),
+        "Value": (str, False),
+        "Value1": (str, False),
+        "Value2": (str, False),
+        "ValueColumn": (str, False),
+        "ViewFrame": (str, False),
+    }
+
+
 class Action(AWSProperty):
     props = {
         "Operation": (str, True),
-        "Parameters": (dict, False),
+        "Parameters": (RecipeParameters, False),
     }
 
 
diff --git a/troposphere/ec2.py b/troposphere/ec2.py
index fcfeff78..c18c16c7 100644
--- a/troposphere/ec2.py
+++ b/troposphere/ec2.py
@@ -1084,6 +1084,8 @@ class VPCCidrBlock(AWSObject):
     props = {
         "AmazonProvidedIpv6CidrBlock": (boolean, False),
         "CidrBlock": (str, False),
+        "Ipv6CidrBlock": (str, False),
+        "Ipv6Pool": (str, False),
         "VpcId": (str, True),
     }
 
diff --git a/troposphere/elasticsearch.py b/troposphere/elasticsearch.py
index de6effa0..51adaa3d 100644
--- a/troposphere/elasticsearch.py
+++ b/troposphere/elasticsearch.py
@@ -68,6 +68,12 @@ class EBSOptions(AWSProperty):
             raise ValueError("Must specify Iops if VolumeType is 'io1'.")
 
 
+class ColdStorageOptions(AWSProperty):
+    props = {
+        "Enabled": (boolean, False),
+    }
+
+
 class ZoneAwarenessConfig(AWSProperty):
     props = {
         "AvailabilityZoneCount": (integer, False),
@@ -76,6 +82,7 @@ class ZoneAwarenessConfig(AWSProperty):
 
 class ElasticsearchClusterConfig(AWSProperty):
     props = {
+        "ColdStorageOptions": (ColdStorageOptions, False),
         "DedicatedMasterCount": (integer, False),
         "DedicatedMasterEnabled": (boolean, False),
         "DedicatedMasterType": (str, False),
diff --git a/troposphere/fsx.py b/troposphere/fsx.py
index cb8ae3c6..42029f84 100644
--- a/troposphere/fsx.py
+++ b/troposphere/fsx.py
@@ -63,6 +63,14 @@ class LustreConfiguration(AWSProperty):
     }
 
 
+class AuditLogConfiguration(AWSProperty):
+    props = {
+        "AuditLogDestination": (str, False),
+        "FileAccessAuditLogLevel": (str, True),
+        "FileShareAccessAuditLogLevel": (str, True),
+    }
+
+
 class SelfManagedActiveDirectoryConfiguration(AWSProperty):
     props = {
         "DnsIps": ([str], False),
@@ -78,6 +86,7 @@ class WindowsConfiguration(AWSProperty):
     props = {
         "ActiveDirectoryId": (str, False),
         "Aliases": ([str], False),
+        "AuditLogConfiguration": (AuditLogConfiguration, False),
         "AutomaticBackupRetentionDays": (integer, False),
         "CopyTagsToBackups": (boolean, False),
         "DailyAutomaticBackupStartTime": (str, False),
diff --git a/troposphere/logs.py b/troposphere/logs.py
index 4391f7e6..306b9707 100644
--- a/troposphere/logs.py
+++ b/troposphere/logs.py
@@ -1,6 +1,36 @@
 from . import AWSObject, AWSProperty
+from .compat import policytypes
 from .constants import LOGS_ALLOWED_RETENTION_DAYS as RETENTION_DAYS
-from .validators import integer_list_item
+from .validators import integer_list_item, json_checker
+import json
+
+policytypes = policytypes + (str,)
+
+
+def validate_resource_policy(policy_document):
+    """validate policy_document. Between 1 to 5120"""
+
+    if not isinstance(policy_document, policytypes):
+        raise ValueError("PolicyDocument must be a valid policy document")
+
+    if isinstance(policy_document, str) and not json_checker(policy_document):
+        raise ValueError("PolicyDocument must be a valid JSON formated string")
+
+    if isinstance(policy_document, dict):
+        policy_document_text = json.dumps(policy_document)
+    elif isinstance(policy_document, str):
+        policy_document_text = policy_document
+    else:
+        policy_document_text = policy_document.to_json()
+
+    # NB: {} empty dict is 2 length
+    if len(policy_document_text) < 3:
+        raise ValueError("PolicyDocument must not be empty")
+
+    if len(policy_document_text) > 5120:
+        raise ValueError("PolicyDocument maximum length must not exceed 5120")
+
+    return policy_document
 
 
 class Destination(AWSObject):
@@ -59,6 +89,15 @@ class QueryDefinition(AWSObject):
     }
 
 
+class ResourcePolicy(AWSObject):
+    resource_type = "AWS::Logs::ResourcePolicy"
+
+    props = {
+        "PolicyDocument": (str, True),
+        "PolicyName": (str, True),
+    }
+
+
 class SubscriptionFilter(AWSObject):
     resource_type = "AWS::Logs::SubscriptionFilter"
 
@@ -68,3 +107,12 @@ class SubscriptionFilter(AWSObject):
         "LogGroupName": (str, True),
         "RoleArn": (str, False),
     }
+
+
+class LogResourcePolicy(AWSObject):
+    resource_type = "AWS::Logs::ResourcePolicy"
+
+    props = {
+        "PolicyDocument": (validate_resource_policy, True),
+        "PolicyName": (str, True),
+    }
diff --git a/troposphere/lookoutequipment.py b/troposphere/lookoutequipment.py
new file mode 100644
index 00000000..488f1fdc
--- /dev/null
+++ b/troposphere/lookoutequipment.py
@@ -0,0 +1,29 @@
+# Copyright (c) 2012-2021, Mark Peek <mark@peek.org>
+# All rights reserved.
+#
+# See LICENSE file for full license.
+#
+# *** Do not modify - this file is autogenerated ***
+# Resource specification version: 39.7.0
+
+
+from troposphere import Tags
+
+from . import AWSObject
+from .validators import integer
+
+
+class InferenceScheduler(AWSObject):
+    resource_type = "AWS::LookoutEquipment::InferenceScheduler"
+
+    props = {
+        "DataDelayOffsetInMinutes": (integer, False),
+        "DataInputConfiguration": (dict, True),
+        "DataOutputConfiguration": (dict, True),
+        "DataUploadFrequency": (str, True),
+        "InferenceSchedulerName": (str, False),
+        "ModelName": (str, True),
+        "RoleArn": (str, True),
+        "ServerSideKmsKeyId": (str, False),
+        "Tags": (Tags, False),
+    }
diff --git a/troposphere/msk.py b/troposphere/msk.py
index d489f468..7896ae16 100644
--- a/troposphere/msk.py
+++ b/troposphere/msk.py
@@ -33,6 +33,10 @@ class BrokerNodeGroupInfo(AWSProperty):
     }
 
 
+class Iam(AWSProperty):
+    props = {"Enabled": (boolean, True)}
+
+
 class Scram(AWSProperty):
     props = {
         "Enabled": (boolean, True),
@@ -41,7 +45,8 @@ class Scram(AWSProperty):
 
 class Sasl(AWSProperty):
     props = {
-        "Scram": (Scram, True),
+        "Iam": (Iam, False),
+        "Scram": (Scram, False),
     }
 
 
diff --git a/troposphere/networkfirewall.py b/troposphere/networkfirewall.py
index 91cdd2f5..a399dc1b 100644
--- a/troposphere/networkfirewall.py
+++ b/troposphere/networkfirewall.py
@@ -12,6 +12,17 @@ from troposphere import Tags
 from . import AWSObject, AWSProperty
 from .validators import boolean, integer
 
+VALID_RULE_GROUP_TYPES = ("STATEFUL", "STATELESS")
+
+
+def validate_rule_group_type(rule_group_type):
+    """Validate Type for RuleGroup"""
+    if rule_group_type not in VALID_RULE_GROUP_TYPES:
+        raise ValueError(
+            "RuleGroup Type must be one of %s" % ", ".join(VALID_RULE_GROUP_TYPES)
+        )
+    return rule_group_type
+
 
 class SubnetMapping(AWSProperty):
     props = {
@@ -73,7 +84,7 @@ class StatelessRuleGroupReference(AWSProperty):
     }
 
 
-class FirewallPolicy(AWSProperty):
+class FirewallPolicyProperty(AWSProperty):
     props = {
         "StatefulRuleGroupReferences": ([StatefulRuleGroupReference], False),
         "StatelessCustomActions": ([CustomAction], False),
@@ -83,6 +94,17 @@ class FirewallPolicy(AWSProperty):
     }
 
 
+class FirewallPolicy(AWSObject):
+    resource_type = "AWS::NetworkFirewall::FirewallPolicy"
+
+    props = {
+        "Description": (str, False),
+        "FirewallPolicy": (FirewallPolicyProperty, True),
+        "FirewallPolicyName": (str, True),
+        "Tags": (Tags, False),
+    }
+
+
 class LogDestinationConfig(AWSProperty):
     props = {
         "LogDestination": (dict, True),
@@ -199,8 +221,21 @@ class RulesSource(AWSProperty):
     }
 
 
-class RuleGroup(AWSProperty):
+class RuleGroupProperty(AWSProperty):
     props = {
         "RuleVariables": (RuleVariables, False),
         "RulesSource": (RulesSource, True),
     }
+
+
+class RuleGroup(AWSObject):
+    resource_type = "AWS::NetworkFirewall::RuleGroup"
+
+    props = {
+        "Capacity": (integer, True),
+        "Description": (str, False),
+        "RuleGroup": (RuleGroupProperty, False),
+        "RuleGroupName": (str, True),
+        "Tags": (Tags, False),
+        "Type": (validate_rule_group_type, True),
+    }
diff --git a/troposphere/qldb.py b/troposphere/qldb.py
index b03fb785..e29873ff 100644
--- a/troposphere/qldb.py
+++ b/troposphere/qldb.py
@@ -1,10 +1,10 @@
-# Copyright (c) 2012-2019, Mark Peek <mark@peek.org>
+# Copyright (c) 2012-2021, Mark Peek <mark@peek.org>
 # All rights reserved.
 #
 # See LICENSE file for full license.
 #
 # *** Do not modify - this file is autogenerated ***
-# Resource specification version: 16.1.0
+# Resource specification version: 39.7.0
 
 
 from troposphere import Tags
@@ -18,6 +18,7 @@ class Ledger(AWSObject):
 
     props = {
         "DeletionProtection": (boolean, False),
+        "KmsKey": (str, False),
         "Name": (str, False),
         "PermissionsMode": (str, True),
         "Tags": (Tags, False),
diff --git a/troposphere/redshift.py b/troposphere/redshift.py
index 77e8ff9d..ecf8e06b 100644
--- a/troposphere/redshift.py
+++ b/troposphere/redshift.py
@@ -1,12 +1,25 @@
-# Copyright (c) 2014, Guillem Anguera <ganguera@gmail.com>
+# Copyright (c) 2012-2021, Mark Peek <mark@peek.org>
 # All rights reserved.
 #
 # See LICENSE file for full license.
+#
+# *** Do not modify - this file is autogenerated ***
+# Resource specification version: 40.0.0
+
+
+from troposphere import Tags
 
-from . import AWSObject, AWSProperty, Tags
+from . import AWSObject, AWSProperty
 from .validators import boolean, integer
 
 
+class Endpoint(AWSProperty):
+    props = {
+        "Address": (str, False),
+        "Port": (str, False),
+    }
+
+
 class LoggingProperties(AWSProperty):
     props = {
         "BucketName": (str, True),
@@ -19,34 +32,54 @@ class Cluster(AWSObject):
 
     props = {
         "AllowVersionUpgrade": (boolean, False),
+        "AquaConfigurationStatus": (str, False),
         "AutomatedSnapshotRetentionPeriod": (integer, False),
         "AvailabilityZone": (str, False),
+        "AvailabilityZoneRelocation": (boolean, False),
+        "AvailabilityZoneRelocationStatus": (str, False),
+        "Classic": (boolean, False),
         "ClusterIdentifier": (str, False),
         "ClusterParameterGroupName": (str, False),
-        "ClusterSecurityGroups": (list, False),
+        "ClusterSecurityGroups": ([str], False),
         "ClusterSubnetGroupName": (str, False),
         "ClusterType": (str, True),
         "ClusterVersion": (str, False),
         "DBName": (str, True),
+        "DeferMaintenance": (boolean, False),
+        "DeferMaintenanceDuration": (integer, False),
+        "DeferMaintenanceEndTime": (str, False),
+        "DeferMaintenanceIdentifier": (str, False),
+        "DeferMaintenanceStartTime": (str, False),
+        "DestinationRegion": (str, False),
         "ElasticIp": (str, False),
         "Encrypted": (boolean, False),
+        "Endpoint": (Endpoint, False),
+        "EnhancedVpcRouting": (boolean, False),
         "HsmClientCertificateIdentifier": (str, False),
         "HsmConfigurationIdentifier": (str, False),
         "IamRoles": ([str], False),
         "KmsKeyId": (str, False),
         "LoggingProperties": (LoggingProperties, False),
-        "MasterUsername": (str, True),
+        "MaintenanceTrackName": (str, False),
+        "ManualSnapshotRetentionPeriod": (integer, False),
         "MasterUserPassword": (str, True),
+        "MasterUsername": (str, True),
         "NodeType": (str, True),
-        "NumberOfNodes": (integer, False),  # Conditional
+        "NumberOfNodes": (integer, False),
         "OwnerAccount": (str, False),
         "Port": (integer, False),
         "PreferredMaintenanceWindow": (str, False),
         "PubliclyAccessible": (boolean, False),
+        "ResourceAction": (str, False),
+        "RevisionTarget": (str, False),
+        "RotateEncryptionKey": (boolean, False),
         "SnapshotClusterIdentifier": (str, False),
+        "SnapshotCopyGrantName": (str, False),
+        "SnapshotCopyManual": (boolean, False),
+        "SnapshotCopyRetentionPeriod": (integer, False),
         "SnapshotIdentifier": (str, False),
         "Tags": (Tags, False),
-        "VpcSecurityGroupIds": (list, False),
+        "VpcSecurityGroupIds": ([str], False),
     }
 
 
@@ -81,8 +114,8 @@ class ClusterSecurityGroupIngress(AWSObject):
     resource_type = "AWS::Redshift::ClusterSecurityGroupIngress"
 
     props = {
-        "ClusterSecurityGroupName": (str, True),
         "CIDRIP": (str, False),
+        "ClusterSecurityGroupName": (str, True),
         "EC2SecurityGroupName": (str, False),
         "EC2SecurityGroupOwnerId": (str, False),
     }
@@ -93,6 +126,6 @@ class ClusterSubnetGroup(AWSObject):
 
     props = {
         "Description": (str, True),
-        "SubnetIds": (list, True),
+        "SubnetIds": ([str], True),
         "Tags": (Tags, False),
     }
diff --git a/troposphere/route53recoverycontrol.py b/troposphere/route53recoverycontrol.py
new file mode 100644
index 00000000..54ea8b58
--- /dev/null
+++ b/troposphere/route53recoverycontrol.py
@@ -0,0 +1,73 @@
+# Copyright (c) 2012-2021, Mark Peek <mark@peek.org>
+# All rights reserved.
+#
+# See LICENSE file for full license.
+#
+# *** Do not modify - this file is autogenerated ***
+# Resource specification version: 39.8.0
+
+
+from . import AWSObject, AWSProperty
+from .validators import boolean, integer
+
+
+class Cluster(AWSObject):
+    resource_type = "AWS::Route53RecoveryControl::Cluster"
+
+    props = {
+        "Name": (str, False),
+    }
+
+
+class ControlPanel(AWSObject):
+    resource_type = "AWS::Route53RecoveryControl::ControlPanel"
+
+    props = {
+        "ClusterArn": (str, False),
+        "Name": (str, True),
+    }
+
+
+class RoutingControl(AWSObject):
+    resource_type = "AWS::Route53RecoveryControl::RoutingControl"
+
+    props = {
+        "ClusterArn": (str, False),
+        "ControlPanelArn": (str, False),
+        "Name": (str, True),
+    }
+
+
+class AssertionRule(AWSProperty):
+    props = {
+        "AssertedControls": ([str], True),
+        "WaitPeriodMs": (integer, True),
+    }
+
+
+class GatingRule(AWSProperty):
+    props = {
+        "GatingControls": ([str], True),
+        "TargetControls": ([str], True),
+        "WaitPeriodMs": (integer, True),
+    }
+
+
+class RuleConfig(AWSProperty):
+    props = {
+        "Inverted": (boolean, True),
+        "Threshold": (integer, True),
+        "Type": (str, True),
+    }
+
+
+class SafetyRule(AWSObject):
+    resource_type = "AWS::Route53RecoveryControl::SafetyRule"
+
+    props = {
+        "AssertionRule": (AssertionRule, False),
+        "ControlPanelArn": (str, True),
+        "GatingRule": (GatingRule, False),
+        "Name": (str, True),
+        "RuleConfig": (RuleConfig, True),
+    }
diff --git a/troposphere/route53recoveryreadiness.py b/troposphere/route53recoveryreadiness.py
new file mode 100644
index 00000000..7f75407e
--- /dev/null
+++ b/troposphere/route53recoveryreadiness.py
@@ -0,0 +1,92 @@
+# Copyright (c) 2012-2021, Mark Peek <mark@peek.org>
+# All rights reserved.
+#
+# See LICENSE file for full license.
+#
+# *** Do not modify - this file is autogenerated ***
+# Resource specification version: 39.8.0
+
+
+from troposphere import Tags
+
+from . import AWSObject, AWSProperty
+
+
+class Cell(AWSObject):
+    resource_type = "AWS::Route53RecoveryReadiness::Cell"
+
+    props = {
+        "CellName": (str, True),
+        "Cells": ([str], False),
+        "Tags": (Tags, False),
+    }
+
+
+class ReadinessCheck(AWSObject):
+    resource_type = "AWS::Route53RecoveryReadiness::ReadinessCheck"
+
+    props = {
+        "ReadinessCheckName": (str, True),
+        "ResourceSetName": (str, False),
+        "Tags": (Tags, False),
+    }
+
+
+class RecoveryGroup(AWSObject):
+    resource_type = "AWS::Route53RecoveryReadiness::RecoveryGroup"
+
+    props = {
+        "Cells": ([str], False),
+        "RecoveryGroupName": (str, True),
+        "Tags": (Tags, False),
+    }
+
+
+class NLBResource(AWSProperty):
+    props = {
+        "Arn": (str, False),
+    }
+
+
+class R53ResourceRecord(AWSProperty):
+    props = {
+        "DomainName": (str, False),
+        "RecordSetId": (str, False),
+    }
+
+
+class TargetResource(AWSProperty):
+    props = {
+        "NLBResource": (NLBResource, False),
+        "R53Resource": (R53ResourceRecord, False),
+    }
+
+
+class DNSTargetResource(AWSProperty):
+    props = {
+        "DomainName": (str, False),
+        "HostedZoneArn": (str, False),
+        "RecordSetId": (str, False),
+        "RecordType": (str, False),
+        "TargetResource": (TargetResource, False),
+    }
+
+
+class Resource(AWSProperty):
+    props = {
+        "ComponentId": (str, False),
+        "DnsTargetResource": (DNSTargetResource, False),
+        "ReadinessScopes": ([str], False),
+        "ResourceArn": (str, False),
+    }
+
+
+class ResourceSet(AWSObject):
+    resource_type = "AWS::Route53RecoveryReadiness::ResourceSet"
+
+    props = {
+        "ResourceSetName": (str, True),
+        "ResourceSetType": (str, True),
+        "Resources": ([Resource], True),
+        "Tags": (Tags, False),
+    }
diff --git a/troposphere/s3outposts.py b/troposphere/s3outposts.py
index f4003e34..2e27a559 100644
--- a/troposphere/s3outposts.py
+++ b/troposphere/s3outposts.py
@@ -4,7 +4,7 @@
 # See LICENSE file for full license.
 #
 # *** Do not modify - this file is autogenerated ***
-# Resource specification version: 31.0.0
+# Resource specification version: 39.8.0
 
 
 from troposphere import Tags
@@ -77,6 +77,8 @@ class Endpoint(AWSObject):
     resource_type = "AWS::S3Outposts::Endpoint"
 
     props = {
+        "AccessType": (str, False),
+        "CustomerOwnedIpv4Pool": (str, False),
         "OutpostId": (str, True),
         "SecurityGroupId": (str, True),
         "SubnetId": (str, True),
diff --git a/troposphere/sagemaker.py b/troposphere/sagemaker.py
index d63eac93..bddfb3a2 100644
--- a/troposphere/sagemaker.py
+++ b/troposphere/sagemaker.py
@@ -341,6 +341,34 @@ class Endpoint(AWSObject):
     }
 
 
+class AsyncInferenceClientConfig(AWSProperty):
+    props = {
+        "MaxConcurrentInvocationsPerInstance": (integer, False),
+    }
+
+
+class AsyncInferenceNotificationConfig(AWSProperty):
+    props = {
+        "ErrorTopic": (str, False),
+        "SuccessTopic": (str, False),
+    }
+
+
+class AsyncInferenceOutputConfig(AWSProperty):
+    props = {
+        "KmsKeyId": (str, False),
+        "NotificationConfig": (AsyncInferenceNotificationConfig, False),
+        "S3OutputPath": (str, True),
+    }
+
+
+class AsyncInferenceConfig(AWSProperty):
+    props = {
+        "ClientConfig": (AsyncInferenceClientConfig, False),
+        "OutputConfig": (AsyncInferenceOutputConfig, True),
+    }
+
+
 class CaptureContentTypeHeader(AWSProperty):
     props = {
         "CsvContentTypes": ([str], False),
@@ -379,6 +407,7 @@ class EndpointConfig(AWSObject):
     resource_type = "AWS::SageMaker::EndpointConfig"
 
     props = {
+        "AsyncInferenceConfig": (AsyncInferenceConfig, False),
         "DataCaptureConfig": (DataCaptureConfig, False),
         "EndpointConfigName": (str, False),
         "KmsKeyId": (str, False),
diff --git a/troposphere/signer.py b/troposphere/signer.py
new file mode 100644
index 00000000..1972aaec
--- /dev/null
+++ b/troposphere/signer.py
@@ -0,0 +1,42 @@
+# Copyright (c) 2012-2021, Mark Peek <mark@peek.org>
+# All rights reserved.
+#
+# See LICENSE file for full license.
+#
+# *** Do not modify - this file is autogenerated ***
+# Resource specification version: 39.8.0
+
+
+from troposphere import Tags
+
+from . import AWSObject, AWSProperty
+from .validators import integer
+
+
+class ProfilePermission(AWSObject):
+    resource_type = "AWS::Signer::ProfilePermission"
+
+    props = {
+        "Action": (str, True),
+        "Principal": (str, True),
+        "ProfileName": (str, True),
+        "ProfileVersion": (str, False),
+        "StatementId": (str, True),
+    }
+
+
+class SignatureValidityPeriod(AWSProperty):
+    props = {
+        "Type": (str, False),
+        "Value": (integer, False),
+    }
+
+
+class SigningProfile(AWSObject):
+    resource_type = "AWS::Signer::SigningProfile"
+
+    props = {
+        "PlatformId": (str, True),
+        "SignatureValidityPeriod": (SignatureValidityPeriod, False),
+        "Tags": (Tags, False),
+    }
diff --git a/troposphere/validators.py b/troposphere/validators.py
index f47f4080..843d1cc7 100644
--- a/troposphere/validators.py
+++ b/troposphere/validators.py
@@ -396,7 +396,7 @@ def statistic_type(statistic):
 
 
 def key_usage_type(key):
-    valid_values = ["ENCRYPT_DECRYPT"]
+    valid_values = ["ENCRYPT_DECRYPT", "SIGN_VERIFY"]
     if key not in valid_values:
         raise ValueError('KeyUsage must be one of: "%s"' % (", ".join(valid_values)))
     return key
diff --git a/troposphere/wafv2.py b/troposphere/wafv2.py
index 50a40bc1..d42d384d 100644
--- a/troposphere/wafv2.py
+++ b/troposphere/wafv2.py
@@ -7,6 +7,7 @@ from . import AWSObject, AWSProperty, Tags
 from .validators import (
     boolean,
     integer,
+    json_checker,
     wafv2_custom_body_response_content,
     wafv2_custom_body_response_content_type,
 )
@@ -215,6 +216,7 @@ class ManagedRuleGroupStatement(AWSProperty):
         "ExcludedRules": ([ExcludedRule], False),
         "Name": (str, False),
         "VendorName": (str, False),
+        "Version": (str, False),
     }
 
 
@@ -395,6 +397,17 @@ class IPSet(AWSObject):
     }
 
 
+class LoggingConfiguration(AWSObject):
+    resource_type = "AWS::WAFv2::LoggingConfiguration"
+
+    props = {
+        "LogDestinationConfigs": ([str], True),
+        "LoggingFilter": (json_checker, False),
+        "RedactedFields": ([FieldToMatch], False),
+        "ResourceArn": (str, True),
+    }
+
+
 class RegexPatternSet(AWSObject):
     resource_type = "AWS::WAFv2::RegexPatternSet"
 


Test patch:
diff --git a/tests/examples_output/RedshiftClusterInVpc.template b/tests/examples_output/RedshiftClusterInVpc.template
index fde1181f..b327445e 100644
--- a/tests/examples_output/RedshiftClusterInVpc.template
+++ b/tests/examples_output/RedshiftClusterInVpc.template
@@ -122,9 +122,11 @@
                         }
                     ]
                 },
-                "VpcSecurityGroupIds": {
-                    "Ref": "SecurityGroup"
-                }
+                "VpcSecurityGroupIds": [
+                    {
+                        "Ref": "SecurityGroup"
+                    }
+                ]
             },
             "Type": "AWS::Redshift::Cluster"
         },
@@ -144,9 +146,11 @@
         "RedshiftClusterSubnetGroup": {
             "Properties": {
                 "Description": "Cluster subnet group",
-                "SubnetIds": {
-                    "Ref": "Subnet"
-                }
+                "SubnetIds": [
+                    {
+                        "Ref": "Subnet"
+                    }
+                ]
             },
             "Type": "AWS::Redshift::ClusterSubnetGroup"
         },
diff --git a/tests/test_logs.py b/tests/test_logs.py
index 5a865097..3fdce641 100644
--- a/tests/test_logs.py
+++ b/tests/test_logs.py
@@ -1,7 +1,7 @@
 import unittest
 
 from troposphere import Retain
-from troposphere.logs import Destination, LogGroup
+from troposphere.logs import Destination, LogGroup, validate_resource_policy, LogResourcePolicy
 
 
 class TestLogs(unittest.TestCase):
@@ -35,6 +35,22 @@ class TestLogs(unittest.TestCase):
         self.assertIn("Type", log_destination_json)
         self.assertIn("Properties", log_destination_json)
 
+    def test_validate_resource_policy(self):
+        for s in ["{ \"Version\": \"2012-10-17\", \"Statement\": [ { \"Sid\": \"Route53LogsToCloudWatchLogs\", \"Effect\": \"Allow\", \"Principal\": { \"Service\": [ \"route53.amazonaws.com\" ] }, \"Action\":\"logs:PutLogEvents\", \"Resource\": \"logArn\" } ] }", {'Version': '2012-10-17', 'Statement': [{'Sid': 'Route53LogsToCloudWatchLogs', 'Effect': 'Allow', 'Principal': {'Service': ['route53.amazonaws.com']}, 'Action': 'logs:PutLogEvents', 'Resource': 'logArn'}]}]:
+            validate_resource_policy(s)
+            log_policy = LogResourcePolicy(
+                "TestLogPolicy",
+                PolicyName='TestLogPolicy',
+                PolicyDocument=s
+            )
+            expected = log_policy.to_dict()
+            properties = expected['Properties']
+            self.assertEqual(properties.get('PolicyDocument'), s)
+
+        for s in ["", "H"*5121, "TEXT", {}]:
+            with self.assertRaises(ValueError):
+                validate_resource_policy(s)
+
 
 if __name__ == "__main__":
     unittest.main()