diff --git a/src/schema/transformers/AddWriteOnlyRequiredPropertiesTransformer.ts b/src/schema/transformers/AddWriteOnlyRequiredPropertiesTransformer.ts new file mode 100644 index 00000000..23eedb9f --- /dev/null +++ b/src/schema/transformers/AddWriteOnlyRequiredPropertiesTransformer.ts @@ -0,0 +1,58 @@ +import type { ResourceSchema } from '../ResourceSchema'; +import type { ResourceTemplateTransformer } from './ResourceTemplateTransformer'; + +/** + * Transformer that adds tabstop placeholders for required write-only properties. + * Only adds placeholders at the required property level, not for nested write-only children. + * Replaces empty objects with tabstop placeholders using LSP snippet syntax. + * Uses sequential tabstops (${1}, ${2}, etc.). The $0 final cursor position is handled by the client. + */ +export class AddWriteOnlyRequiredPropertiesTransformer implements ResourceTemplateTransformer { + public transform(resourceProperties: Record, schema: ResourceSchema): void { + const requiredProps = schema.required ?? []; + const writeOnlyPaths = schema.writeOnlyProperties ?? []; + + if (requiredProps.length === 0 || writeOnlyPaths.length === 0) { + return; + } + + const requiredWriteOnlyProps = new Set(); + + for (const path of writeOnlyPaths) { + const parts = this.parseJsonPointer(path); + if (parts.length >= 2 && parts[0] === 'properties') { + const rootProp = parts[1]; + if (requiredProps.includes(rootProp)) { + requiredWriteOnlyProps.add(rootProp); + } + } + } + + let tabstopIndex = 1; + for (const prop of requiredWriteOnlyProps) { + if (!(prop in resourceProperties) || this.isEmpty(resourceProperties[prop])) { + resourceProperties[prop] = `\${${tabstopIndex++}:update required write only property}`; + } + } + } + + private isEmpty(value: unknown): boolean { + if (value === null || value === undefined) { + return true; + } + if (typeof value === 'object' && !Array.isArray(value)) { + return Object.keys(value as Record).length === 0; + } + return false; + } + + private parseJsonPointer(pointer: string): string[] { + if (!pointer.startsWith('/')) { + return []; + } + return pointer + .slice(1) + .split('/') + .map((part) => part.replaceAll('~1', '/').replaceAll('~0', '~')); + } +} diff --git a/src/schema/transformers/TransformersUtil.ts b/src/schema/transformers/TransformersUtil.ts index 468345e7..020b2b0a 100644 --- a/src/schema/transformers/TransformersUtil.ts +++ b/src/schema/transformers/TransformersUtil.ts @@ -1,4 +1,5 @@ import { ResourceStatePurpose } from '../../resourceState/ResourceStateTypes'; +import { AddWriteOnlyRequiredPropertiesTransformer } from './AddWriteOnlyRequiredPropertiesTransformer'; import { RemoveMutuallyExclusivePropertiesTransformer } from './RemoveMutuallyExclusivePropertiesTransformer'; import { RemoveReadonlyPropertiesTransformer } from './RemoveReadonlyPropertiesTransformer'; import { RemoveRequiredXorPropertiesTransformer } from './RemoveRequiredXorPropertiesTransformer'; @@ -14,6 +15,7 @@ export class TransformersUtil { new RemoveMutuallyExclusivePropertiesTransformer(), new RemoveSystemTagsTransformer(), new RemoveRequiredXorPropertiesTransformer(), + new AddWriteOnlyRequiredPropertiesTransformer(), ]; } else if (purpose === ResourceStatePurpose.CLONE) { return [ @@ -22,6 +24,7 @@ export class TransformersUtil { new RemoveSystemTagsTransformer(), new ReplacePrimaryIdentifierTransformer(), new RemoveRequiredXorPropertiesTransformer(), + new AddWriteOnlyRequiredPropertiesTransformer(), ]; } return []; diff --git a/tst/resources/schemas/aws-dynamodb-globaltable.json b/tst/resources/schemas/aws-dynamodb-globaltable.json new file mode 100644 index 00000000..e2894465 --- /dev/null +++ b/tst/resources/schemas/aws-dynamodb-globaltable.json @@ -0,0 +1,544 @@ +{ + "handlers" : { + "read" : { + "permissions" : [ "dynamodb:Describe*", "dynamodb:GetResourcePolicy", "application-autoscaling:Describe*", "cloudwatch:PutMetricData", "dynamodb:ListTagsOfResource", "kms:DescribeKey" ] + }, + "create" : { + "permissions" : [ "dynamodb:CreateTable", "dynamodb:CreateTableReplica", "dynamodb:Describe*", "dynamodb:UpdateTimeToLive", "dynamodb:UpdateContributorInsights", "dynamodb:UpdateContinuousBackups", "dynamodb:ListTagsOfResource", "dynamodb:Query", "dynamodb:Scan", "dynamodb:UpdateItem", "dynamodb:PutItem", "dynamodb:GetItem", "dynamodb:DeleteItem", "dynamodb:BatchWriteItem", "dynamodb:TagResource", "dynamodb:EnableKinesisStreamingDestination", "dynamodb:DisableKinesisStreamingDestination", "dynamodb:UpdateTableReplicaAutoScaling", "dynamodb:TagResource", "dynamodb:GetResourcePolicy", "dynamodb:PutResourcePolicy", "application-autoscaling:DeleteScalingPolicy", "application-autoscaling:DeleteScheduledAction", "application-autoscaling:DeregisterScalableTarget", "application-autoscaling:Describe*", "application-autoscaling:PutScalingPolicy", "application-autoscaling:PutScheduledAction", "application-autoscaling:RegisterScalableTarget", "kinesis:ListStreams", "kinesis:DescribeStream", "kinesis:PutRecords", "kms:CreateGrant", "kms:DescribeKey", "kms:ListAliases", "kms:Decrypt", "kms:RevokeGrant", "cloudwatch:PutMetricData", "iam:CreateServiceLinkedRole" ] + }, + "update" : { + "permissions" : [ "dynamodb:Describe*", "dynamodb:CreateTableReplica", "dynamodb:UpdateTable", "dynamodb:UpdateTimeToLive", "dynamodb:UpdateContinuousBackups", "dynamodb:UpdateContributorInsights", "dynamodb:ListTagsOfResource", "dynamodb:Query", "dynamodb:Scan", "dynamodb:UpdateItem", "dynamodb:PutItem", "dynamodb:GetItem", "dynamodb:DeleteItem", "dynamodb:BatchWriteItem", "dynamodb:DeleteTable", "dynamodb:DeleteTableReplica", "dynamodb:UpdateItem", "dynamodb:TagResource", "dynamodb:UntagResource", "dynamodb:EnableKinesisStreamingDestination", "dynamodb:DisableKinesisStreamingDestination", "dynamodb:UpdateTableReplicaAutoScaling", "dynamodb:UpdateKinesisStreamingDestination", "dynamodb:GetResourcePolicy", "dynamodb:PutResourcePolicy", "dynamodb:DeleteResourcePolicy", "application-autoscaling:DeleteScalingPolicy", "application-autoscaling:DeleteScheduledAction", "application-autoscaling:DeregisterScalableTarget", "application-autoscaling:Describe*", "application-autoscaling:PutScalingPolicy", "application-autoscaling:PutScheduledAction", "application-autoscaling:RegisterScalableTarget", "kinesis:ListStreams", "kinesis:DescribeStream", "kinesis:PutRecords", "kms:CreateGrant", "kms:DescribeKey", "kms:ListAliases", "kms:RevokeGrant", "cloudwatch:PutMetricData" ], + "timeoutInMinutes" : 1200 + }, + "list" : { + "permissions" : [ "dynamodb:ListTables", "cloudwatch:PutMetricData" ] + }, + "delete" : { + "permissions" : [ "dynamodb:Describe*", "dynamodb:DeleteTable", "application-autoscaling:DeleteScalingPolicy", "application-autoscaling:DeleteScheduledAction", "application-autoscaling:DeregisterScalableTarget", "application-autoscaling:Describe*", "application-autoscaling:PutScalingPolicy", "application-autoscaling:PutScheduledAction", "application-autoscaling:RegisterScalableTarget" ] + } + }, + "typeName" : "AWS::DynamoDB::GlobalTable", + "readOnlyProperties" : [ "/properties/Arn", "/properties/StreamArn", "/properties/TableId" ], + "description" : "Version: None. Resource Type definition for AWS::DynamoDB::GlobalTable", + "additionalIdentifiers" : [ [ "/properties/Arn" ], [ "/properties/StreamArn" ] ], + "writeOnlyProperties" : [ "/properties/Replicas/*/ReadProvisionedThroughputSettings/ReadCapacityAutoScalingSettings/SeedCapacity", "/properties/Replicas/*/GlobalSecondaryIndexes/*/ReadProvisionedThroughputSettings/ReadCapacityAutoScalingSettings/SeedCapacity", "/properties/WriteProvisionedThroughputSettings/WriteCapacityAutoScalingSettings/SeedCapacity", "/properties/GlobalSecondaryIndexes/*/WriteProvisionedThroughputSettings/WriteCapacityAutoScalingSettings/SeedCapacity" ], + "createOnlyProperties" : [ "/properties/LocalSecondaryIndexes", "/properties/TableName", "/properties/KeySchema" ], + "additionalProperties" : false, + "primaryIdentifier" : [ "/properties/TableName" ], + "definitions" : { + "LocalSecondaryIndex" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "IndexName" : { + "minLength" : 3, + "type" : "string", + "maxLength" : 255 + }, + "Projection" : { + "$ref" : "#/definitions/Projection" + }, + "KeySchema" : { + "maxItems" : 2, + "uniqueItems" : true, + "type" : "array", + "items" : { + "$ref" : "#/definitions/KeySchema" + } + } + }, + "required" : [ "IndexName", "Projection", "KeySchema" ] + }, + "ReplicaSpecification" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "SSESpecification" : { + "$ref" : "#/definitions/ReplicaSSESpecification" + }, + "KinesisStreamSpecification" : { + "$ref" : "#/definitions/KinesisStreamSpecification" + }, + "ContributorInsightsSpecification" : { + "$ref" : "#/definitions/ContributorInsightsSpecification" + }, + "PointInTimeRecoverySpecification" : { + "$ref" : "#/definitions/PointInTimeRecoverySpecification" + }, + "ReplicaStreamSpecification" : { + "$ref" : "#/definitions/ReplicaStreamSpecification" + }, + "GlobalSecondaryIndexes" : { + "uniqueItems" : true, + "insertionOrder" : false, + "type" : "array", + "items" : { + "$ref" : "#/definitions/ReplicaGlobalSecondaryIndexSpecification" + } + }, + "Region" : { + "type" : "string" + }, + "ResourcePolicy" : { + "$ref" : "#/definitions/ResourcePolicy" + }, + "ReadProvisionedThroughputSettings" : { + "$ref" : "#/definitions/ReadProvisionedThroughputSettings" + }, + "TableClass" : { + "type" : "string" + }, + "DeletionProtectionEnabled" : { + "type" : "boolean" + }, + "Tags" : { + "uniqueItems" : true, + "insertionOrder" : false, + "type" : "array", + "items" : { + "$ref" : "#/definitions/Tag" + } + }, + "ReadOnDemandThroughputSettings" : { + "$ref" : "#/definitions/ReadOnDemandThroughputSettings" + } + }, + "required" : [ "Region" ] + }, + "AttributeDefinition" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "AttributeType" : { + "type" : "string" + }, + "AttributeName" : { + "minLength" : 1, + "type" : "string", + "maxLength" : 255 + } + }, + "required" : [ "AttributeName", "AttributeType" ] + }, + "Projection" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "NonKeyAttributes" : { + "maxItems" : 20, + "uniqueItems" : true, + "insertionOrder" : false, + "type" : "array", + "items" : { + "type" : "string" + } + }, + "ProjectionType" : { + "type" : "string" + } + } + }, + "PointInTimeRecoverySpecification" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "PointInTimeRecoveryEnabled" : { + "type" : "boolean" + }, + "RecoveryPeriodInDays" : { + "maximum" : 35, + "type" : "integer", + "minimum" : 1 + } + }, + "dependencies" : { + "RecoveryPeriodInDays" : [ "PointInTimeRecoveryEnabled" ] + } + }, + "ReplicaGlobalSecondaryIndexSpecification" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "IndexName" : { + "minLength" : 3, + "type" : "string", + "maxLength" : 255 + }, + "ContributorInsightsSpecification" : { + "$ref" : "#/definitions/ContributorInsightsSpecification" + }, + "ReadProvisionedThroughputSettings" : { + "$ref" : "#/definitions/ReadProvisionedThroughputSettings" + }, + "ReadOnDemandThroughputSettings" : { + "$ref" : "#/definitions/ReadOnDemandThroughputSettings" + } + }, + "required" : [ "IndexName" ] + }, + "GlobalSecondaryIndex" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "IndexName" : { + "minLength" : 3, + "type" : "string", + "maxLength" : 255 + }, + "Projection" : { + "$ref" : "#/definitions/Projection" + }, + "KeySchema" : { + "minItems" : 1, + "maxItems" : 2, + "uniqueItems" : true, + "type" : "array", + "items" : { + "$ref" : "#/definitions/KeySchema" + } + }, + "WarmThroughput" : { + "$ref" : "#/definitions/WarmThroughput" + }, + "WriteProvisionedThroughputSettings" : { + "$ref" : "#/definitions/WriteProvisionedThroughputSettings" + }, + "WriteOnDemandThroughputSettings" : { + "$ref" : "#/definitions/WriteOnDemandThroughputSettings" + } + }, + "required" : [ "IndexName", "Projection", "KeySchema" ] + }, + "WriteProvisionedThroughputSettings" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "WriteCapacityAutoScalingSettings" : { + "$ref" : "#/definitions/CapacityAutoScalingSettings" + } + } + }, + "WriteOnDemandThroughputSettings" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "MaxWriteRequestUnits" : { + "type" : "integer", + "minimum" : 1 + } + } + }, + "ReplicaStreamSpecification" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "ResourcePolicy" : { + "$ref" : "#/definitions/ResourcePolicy" + } + }, + "required" : [ "ResourcePolicy" ] + }, + "GlobalTableWitness" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "Region" : { + "type" : "string" + } + } + }, + "ReadOnDemandThroughputSettings" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "MaxReadRequestUnits" : { + "type" : "integer", + "minimum" : 1 + } + } + }, + "SSESpecification" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "SSEEnabled" : { + "type" : "boolean" + }, + "SSEType" : { + "type" : "string" + } + }, + "required" : [ "SSEEnabled" ] + }, + "KinesisStreamSpecification" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "ApproximateCreationDateTimePrecision" : { + "type" : "string", + "enum" : [ "MICROSECOND", "MILLISECOND" ] + }, + "StreamArn" : { + "relationshipRef" : { + "typeName" : "AWS::Kinesis::Stream", + "propertyPath" : "/properties/Arn" + }, + "type" : "string" + } + }, + "required" : [ "StreamArn" ] + }, + "StreamSpecification" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "StreamViewType" : { + "type" : "string" + } + }, + "required" : [ "StreamViewType" ] + }, + "ContributorInsightsSpecification" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "Enabled" : { + "type" : "boolean" + } + }, + "required" : [ "Enabled" ] + }, + "CapacityAutoScalingSettings" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "MinCapacity" : { + "type" : "integer", + "minimum" : 1 + }, + "SeedCapacity" : { + "type" : "integer", + "minimum" : 1 + }, + "TargetTrackingScalingPolicyConfiguration" : { + "$ref" : "#/definitions/TargetTrackingScalingPolicyConfiguration" + }, + "MaxCapacity" : { + "type" : "integer", + "minimum" : 1 + } + }, + "required" : [ "MinCapacity", "MaxCapacity", "TargetTrackingScalingPolicyConfiguration" ] + }, + "WarmThroughput" : { + "anyOf" : [ { + "required" : [ "ReadUnitsPerSecond" ] + }, { + "required" : [ "WriteUnitsPerSecond" ] + } ], + "additionalProperties" : false, + "type" : "object", + "properties" : { + "ReadUnitsPerSecond" : { + "type" : "integer", + "minimum" : 1 + }, + "WriteUnitsPerSecond" : { + "type" : "integer", + "minimum" : 1 + } + } + }, + "TargetTrackingScalingPolicyConfiguration" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "ScaleOutCooldown" : { + "type" : "integer", + "minimum" : 0 + }, + "TargetValue" : { + "format" : "double", + "type" : "number" + }, + "DisableScaleIn" : { + "type" : "boolean" + }, + "ScaleInCooldown" : { + "type" : "integer", + "minimum" : 0 + } + }, + "required" : [ "TargetValue" ] + }, + "ReplicaSSESpecification" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "KMSMasterKeyId" : { + "anyOf" : [ { + "relationshipRef" : { + "typeName" : "AWS::KMS::Key", + "propertyPath" : "/properties/Arn" + } + }, { + "relationshipRef" : { + "typeName" : "AWS::KMS::Key", + "propertyPath" : "/properties/KeyId" + } + }, { + "relationshipRef" : { + "typeName" : "AWS::KMS::Alias", + "propertyPath" : "/properties/AliasName" + } + } ], + "type" : "string" + } + }, + "required" : [ "KMSMasterKeyId" ] + }, + "ResourcePolicy" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "PolicyDocument" : { + "type" : "object" + } + }, + "required" : [ "PolicyDocument" ] + }, + "KeySchema" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "KeyType" : { + "type" : "string" + }, + "AttributeName" : { + "minLength" : 1, + "type" : "string", + "maxLength" : 255 + } + }, + "required" : [ "KeyType", "AttributeName" ] + }, + "Tag" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "Value" : { + "type" : "string" + }, + "Key" : { + "type" : "string" + } + }, + "required" : [ "Value", "Key" ] + }, + "ReadProvisionedThroughputSettings" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "ReadCapacityUnits" : { + "type" : "integer", + "minimum" : 1 + }, + "ReadCapacityAutoScalingSettings" : { + "$ref" : "#/definitions/CapacityAutoScalingSettings" + } + } + }, + "TimeToLiveSpecification" : { + "additionalProperties" : false, + "type" : "object", + "properties" : { + "Enabled" : { + "type" : "boolean" + }, + "AttributeName" : { + "type" : "string" + } + }, + "required" : [ "Enabled" ] + } + }, + "required" : [ "KeySchema", "AttributeDefinitions", "Replicas" ], + "properties" : { + "TableId" : { + "type" : "string" + }, + "SSESpecification" : { + "$ref" : "#/definitions/SSESpecification" + }, + "StreamSpecification" : { + "$ref" : "#/definitions/StreamSpecification" + }, + "WarmThroughput" : { + "$ref" : "#/definitions/WarmThroughput" + }, + "Replicas" : { + "minItems" : 1, + "uniqueItems" : true, + "insertionOrder" : false, + "type" : "array", + "items" : { + "$ref" : "#/definitions/ReplicaSpecification" + } + }, + "WriteProvisionedThroughputSettings" : { + "$ref" : "#/definitions/WriteProvisionedThroughputSettings" + }, + "WriteOnDemandThroughputSettings" : { + "$ref" : "#/definitions/WriteOnDemandThroughputSettings" + }, + "TableName" : { + "type" : "string" + }, + "AttributeDefinitions" : { + "minItems" : 1, + "uniqueItems" : true, + "insertionOrder" : false, + "type" : "array", + "items" : { + "$ref" : "#/definitions/AttributeDefinition" + } + }, + "BillingMode" : { + "type" : "string" + }, + "GlobalSecondaryIndexes" : { + "uniqueItems" : true, + "insertionOrder" : false, + "type" : "array", + "items" : { + "$ref" : "#/definitions/GlobalSecondaryIndex" + } + }, + "KeySchema" : { + "minItems" : 1, + "maxItems" : 2, + "uniqueItems" : true, + "type" : "array", + "items" : { + "$ref" : "#/definitions/KeySchema" + } + }, + "LocalSecondaryIndexes" : { + "uniqueItems" : true, + "insertionOrder" : false, + "type" : "array", + "items" : { + "$ref" : "#/definitions/LocalSecondaryIndex" + } + }, + "Arn" : { + "type" : "string" + }, + "StreamArn" : { + "type" : "string" + }, + "TimeToLiveSpecification" : { + "$ref" : "#/definitions/TimeToLiveSpecification" + } + } +} \ No newline at end of file diff --git a/tst/resources/schemas/aws-ec2-spotfleet.json b/tst/resources/schemas/aws-ec2-spotfleet.json new file mode 100644 index 00000000..45b90336 --- /dev/null +++ b/tst/resources/schemas/aws-ec2-spotfleet.json @@ -0,0 +1,812 @@ +{ + "typeName" : "AWS::EC2::SpotFleet", + "description" : "Resource Type definition for AWS::EC2::SpotFleet", + "additionalProperties" : false, + "properties" : { + "Id" : { + "type" : "string" + }, + "SpotFleetRequestConfigData" : { + "$ref" : "#/definitions/SpotFleetRequestConfigData" + } + }, + "definitions" : { + "SpotFleetRequestConfigData" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "AllocationStrategy" : { + "type" : "string", + "enum" : [ "capacityOptimized", "capacityOptimizedPrioritized", "diversified", "lowestPrice", "priceCapacityOptimized" ] + }, + "Context" : { + "type" : "string" + }, + "ExcessCapacityTerminationPolicy" : { + "type" : "string", + "enum" : [ "Default", "NoTermination", "default", "noTermination" ] + }, + "IamFleetRole" : { + "type" : "string" + }, + "InstanceInterruptionBehavior" : { + "type" : "string", + "enum" : [ "hibernate", "stop", "terminate" ] + }, + "InstancePoolsToUseCount" : { + "type" : "integer" + }, + "LaunchSpecifications" : { + "type" : "array", + "uniqueItems" : true, + "items" : { + "$ref" : "#/definitions/SpotFleetLaunchSpecification" + } + }, + "LaunchTemplateConfigs" : { + "type" : "array", + "uniqueItems" : true, + "items" : { + "$ref" : "#/definitions/LaunchTemplateConfig" + } + }, + "LoadBalancersConfig" : { + "$ref" : "#/definitions/LoadBalancersConfig" + }, + "OnDemandAllocationStrategy" : { + "type" : "string" + }, + "OnDemandMaxTotalPrice" : { + "type" : "string" + }, + "OnDemandTargetCapacity" : { + "type" : "integer" + }, + "ReplaceUnhealthyInstances" : { + "type" : "boolean" + }, + "SpotMaintenanceStrategies" : { + "$ref" : "#/definitions/SpotMaintenanceStrategies" + }, + "SpotMaxTotalPrice" : { + "type" : "string" + }, + "SpotPrice" : { + "type" : "string" + }, + "TargetCapacity" : { + "type" : "integer" + }, + "TerminateInstancesWithExpiration" : { + "type" : "boolean" + }, + "Type" : { + "type" : "string", + "enum" : [ "maintain", "request" ] + }, + "ValidFrom" : { + "type" : "string" + }, + "ValidUntil" : { + "type" : "string" + }, + "TagSpecifications" : { + "type" : "array", + "uniqueItems" : true, + "items" : { + "$ref" : "#/definitions/SpotFleetTagSpecification" + } + }, + "TargetCapacityUnitType" : { + "type" : "string", + "enum" : [ "vcpu", "memory-mib", "units" ] + } + }, + "required" : [ "IamFleetRole", "TargetCapacity" ] + }, + "SpotFleetLaunchSpecification" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "BlockDeviceMappings" : { + "type" : "array", + "uniqueItems" : true, + "items" : { + "$ref" : "#/definitions/BlockDeviceMapping" + } + }, + "EbsOptimized" : { + "type" : "boolean", + "default" : false + }, + "IamInstanceProfile" : { + "$ref" : "#/definitions/IamInstanceProfileSpecification" + }, + "ImageId" : { + "type" : "string" + }, + "InstanceType" : { + "type" : "string" + }, + "KernelId" : { + "type" : "string" + }, + "KeyName" : { + "type" : "string" + }, + "Monitoring" : { + "$ref" : "#/definitions/SpotFleetMonitoring" + }, + "NetworkInterfaces" : { + "type" : "array", + "uniqueItems" : true, + "items" : { + "$ref" : "#/definitions/InstanceNetworkInterfaceSpecification" + } + }, + "Placement" : { + "$ref" : "#/definitions/SpotPlacement" + }, + "RamdiskId" : { + "type" : "string" + }, + "SecurityGroups" : { + "type" : "array", + "uniqueItems" : true, + "items" : { + "$ref" : "#/definitions/GroupIdentifier" + } + }, + "SpotPrice" : { + "type" : "string" + }, + "SubnetId" : { + "type" : "string" + }, + "TagSpecifications" : { + "type" : "array", + "uniqueItems" : true, + "items" : { + "$ref" : "#/definitions/SpotFleetTagSpecification" + } + }, + "UserData" : { + "type" : "string" + }, + "WeightedCapacity" : { + "type" : "number" + }, + "InstanceRequirements" : { + "$ref" : "#/definitions/InstanceRequirementsRequest" + } + }, + "required" : [ "ImageId" ] + }, + "LoadBalancersConfig" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "ClassicLoadBalancersConfig" : { + "$ref" : "#/definitions/ClassicLoadBalancersConfig" + }, + "TargetGroupsConfig" : { + "$ref" : "#/definitions/TargetGroupsConfig" + } + } + }, + "SpotMaintenanceStrategies" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "CapacityRebalance" : { + "$ref" : "#/definitions/SpotCapacityRebalance" + } + } + }, + "SpotCapacityRebalance" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "ReplacementStrategy" : { + "type" : "string", + "enum" : [ "launch", "launch-before-terminate" ] + }, + "TerminationDelay" : { + "type" : "integer" + } + } + }, + "Lau nchTemplateConfig" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "LaunchTemplateSpecification" : { + "$ref" : "#/definitions/FleetLaunchTemplateSpecification" + }, + "Overrides" : { + "type" : "array", + "uniqueItems" : true, + "items" : { + "$ref" : "#/definitions/LaunchTemplateOverrides" + } + } + } + }, + "SpotFleetTagSpecification" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "ResourceType" : { + "type" : "string", + "enum" : [ "client-vpn-endpoint", "customer-gateway", "dedicated-host", "dhcp-options", "egress-only-internet-gateway", "elastic-gpu", "elastic-ip", "export-image-task", "export-instance-task", "fleet", "fpga-image", "host-reservation", "image", "import-image-task", "import-snapshot-task", "instance", "internet-gateway", "key-pair", "launch-template", "local-gateway-route-table-vpc-association", "natgateway", "network-acl", "network-insights-analysis", "network-insights-path", "network-interface", "placement-group", "reserved-instances", "route-table", "security-group", "snapshot", "spot-fleet-request", "spot-instances-request", "subnet", "traffic-mirror-filter", "traffic-mirror-session", "traffic-mirror-target", "transit-gateway", "transit-gateway-attachment", "transit-gateway-connect-peer", "transit-gateway-multicast-domain", "transit-gateway-route-table", "volume", "vpc", "vpc-flow-log", "vpc-peering-connection", "vpn-connection", "vpn-gateway" ] + }, + "Tags" : { + "type" : "array", + "uniqueItems" : false, + "items" : { + "$ref" : "#/definitions/Tag" + } + } + } + }, + "FleetLaunchTemplateSpecification" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "LaunchTemplateId" : { + "type" : "string" + }, + "LaunchTemplateName" : { + "type" : "string", + "minLength" : 3, + "maxLength" : 128, + "pattern" : "[a-zA-Z0-9\\(\\)\\.\\-/_]+" + }, + "Version" : { + "type" : "string" + } + }, + "required" : [ "Version" ] + }, + "GroupIdentifier" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "GroupId" : { + "type" : "string" + } + }, + "required" : [ "GroupId" ] + }, + "IamInstanceProfileSpecification" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "Arn" : { + "type" : "string" + } + } + }, + "ClassicLoadBalancersConfig" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "ClassicLoadBalancers" : { + "type" : "array", + "uniqueItems" : true, + "items" : { + "$ref" : "#/definitions/ClassicLoadBalancer" + } + } + }, + "required" : [ "ClassicLoadBalancers" ] + }, + "LaunchTemplateOverrides" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "AvailabilityZone" : { + "type" : "string" + }, + "InstanceType" : { + "type" : "string" + }, + "SpotPrice" : { + "type" : "string" + }, + "SubnetId" : { + "type" : "string" + }, + "WeightedCapacity" : { + "type" : "number" + }, + "InstanceRequirements" : { + "$ref" : "#/definitions/InstanceRequirementsRequest" + }, + "Priority" : { + "type" : "number" + } + } + }, + "SpotFleetMonitoring" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "Enabled" : { + "type" : "boolean", + "default" : false + } + } + }, + "SpotPlacement" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "AvailabilityZone" : { + "type" : "string" + }, + "GroupName" : { + "type" : "string" + }, + "Tenancy" : { + "type" : "string", + "enum" : [ "dedicated", "default", "host" ] + } + } + }, + "InstanceNetworkInterfaceSpecification" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "AssociatePublicIpAddress" : { + "type" : "boolean" + }, + "DeleteOnTermination" : { + "type" : "boolean" + }, + "Description" : { + "type" : "string" + }, + "DeviceIndex" : { + "type" : "integer" + }, + "Groups" : { + "type" : "array", + "uniqueItems" : true, + "items" : { + "type" : "string" + } + }, + "Ipv6AddressCount" : { + "type" : "integer" + }, + "Ipv6Addresses" : { + "type" : "array", + "uniqueItems" : true, + "items" : { + "$ref" : "#/definitions/InstanceIpv6Address" + } + }, + "NetworkInterfaceId" : { + "type" : "string" + }, + "PrivateIpAddresses" : { + "type" : "array", + "uniqueItems" : true, + "items" : { + "$ref" : "#/definitions/PrivateIpAddressSpecification" + } + }, + "SecondaryPrivateIpAddressCount" : { + "type" : "integer" + }, + "SubnetId" : { + "type" : "string" + } + } + }, + "BlockDeviceMapping" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "DeviceName" : { + "type" : "string" + }, + "Ebs" : { + "$ref" : "#/definitions/EbsBlockDevice" + }, + "NoDevice" : { + "type" : "string" + }, + "VirtualName" : { + "type" : "string" + } + }, + "required" : [ "DeviceName" ] + }, + "TargetGroupsConfig" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "TargetGroups" : { + "type" : "array", + "uniqueItems" : true, + "items" : { + "$ref" : "#/definitions/TargetGroup" + } + } + }, + "required" : [ "TargetGroups" ] + }, + "EbsBlockDevice" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "DeleteOnTermination" : { + "type" : "boolean" + }, + "Encrypted" : { + "type" : "boolean" + }, + "Iops" : { + "type" : "integer" + }, + "SnapshotId" : { + "type" : "string" + }, + "VolumeSize" : { + "type" : "integer" + }, + "VolumeType" : { + "type" : "string", + "enum" : [ "gp2", "gp3", "io1", "io2", "sc1", "st1", "standard" ] + } + } + }, + "TargetGroup" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "Arn" : { + "type" : "string" + } + }, + "required" : [ "Arn" ] + }, + "Tag" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "Key" : { + "type" : "string" + }, + "Value" : { + "type" : "string" + } + }, + "required" : [ "Value", "Key" ] + }, + "PrivateIpAddressSpecification" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "Primary" : { + "type" : "boolean" + }, + "PrivateIpAddress" : { + "type" : "string" + } + }, + "required" : [ "PrivateIpAddress" ] + }, + "ClassicLoadBalancer" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "Name" : { + "type" : "string" + } + }, + "required" : [ "Name" ] + }, + "InstanceIpv6Address" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "Ipv6Address" : { + "type" : "string" + } + }, + "required" : [ "Ipv6Address" ] + }, + "InstanceRequirementsRequest" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "VCpuCount" : { + "$ref" : "#/definitions/VCpuCountRangeRequest" + }, + "MemoryMiB" : { + "$ref" : "#/definitions/MemoryMiBRequest" + }, + "CpuManufacturers" : { + "type" : "array", + "uniqueItems" : false, + "items" : { + "type" : "string", + "enum" : [ "intel", "amd", "amazon-web-services", "apple" ] + } + }, + "MemoryGiBPerVCpu" : { + "$ref" : "#/definitions/MemoryGiBPerVCpuRequest" + }, + "AllowedInstanceTypes" : { + "type" : "array", + "uniqueItems" : false, + "items" : { + "type" : "string", + "minLength" : 1, + "maxLength" : 30, + "pattern" : "[a-zA-Z0-9\\.\\*]+" + } + }, + "ExcludedInstanceTypes" : { + "type" : "array", + "uniqueItems" : false, + "items" : { + "type" : "string", + "minLength" : 1, + "maxLength" : 30, + "pattern" : "[a-zA-Z0-9\\.\\*]+" + } + }, + "InstanceGenerations" : { + "type" : "array", + "uniqueItems" : false, + "items" : { + "type" : "string", + "enum" : [ "current", "previous" ] + } + }, + "SpotMaxPricePercentageOverLowestPrice" : { + "type" : "integer" + }, + "OnDemandMaxPricePercentageOverLowestPrice" : { + "type" : "integer" + }, + "MaxSpotPriceAsPercentageOfOptimalOnDemandPrice" : { + "type" : "integer" + }, + "BareMetal" : { + "type" : "string", + "enum" : [ "included", "required", "excluded" ] + }, + "BurstablePerformance" : { + "type" : "string", + "enum" : [ "included", "required", "excluded" ] + }, + "RequireHibernateSupport" : { + "type" : "boolean" + }, + "NetworkBandwidthGbps" : { + "$ref" : "#/definitions/NetworkBandwidthGbpsRequest" + }, + "NetworkInterfaceCount" : { + "$ref" : "#/definitions/NetworkInterfaceCountRequest" + }, + "LocalStorage" : { + "type" : "string", + "enum" : [ "included", "required", "excluded" ] + }, + "LocalStorageTypes" : { + "type" : "array", + "uniqueItems" : false, + "items" : { + "type" : "string", + "enum" : [ "hdd", "ssd" ] + } + }, + "TotalLocalStorageGB" : { + "$ref" : "#/definitions/TotalLocalStorageGBRequest" + }, + "BaselineEbsBandwidthMbps" : { + "$ref" : "#/definitions/BaselineEbsBandwidthMbpsRequest" + }, + "AcceleratorTypes" : { + "type" : "array", + "uniqueItems" : false, + "items" : { + "type" : "string", + "enum" : [ "gpu", "fpga", "inference" ] + } + }, + "AcceleratorCount" : { + "$ref" : "#/definitions/AcceleratorCountRequest" + }, + "AcceleratorManufacturers" : { + "type" : "array", + "uniqueItems" : false, + "items" : { + "type" : "string", + "enum" : [ "amazon-web-services", "amd", "habana", "nvidia", "xilinx" ] + } + }, + "AcceleratorNames" : { + "type" : "array", + "uniqueItems" : false, + "items" : { + "type" : "string", + "enum" : [ "a10g", "a100", "h100", "inferentia", "k520", "k80", "m60", "radeon-pro-v520", "t4", "t4g", "vu9p", "v100" ] + } + }, + "AcceleratorTotalMemoryMiB" : { + "$ref" : "#/definitions/AcceleratorTotalMemoryMiBRequest" + }, + "BaselinePerformanceFactors" : { + "$ref" : "#/definitions/BaselinePerformanceFactorsRequest" + } + } + }, + "VCpuCountRangeRequest" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "Min" : { + "type" : "integer" + }, + "Max" : { + "type" : "integer" + } + } + }, + "MemoryMiBRequest" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "Min" : { + "type" : "integer" + }, + "Max" : { + "type" : "integer" + } + } + }, + "MemoryGiBPerVCpuRequest" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "Min" : { + "type" : "number" + }, + "Max" : { + "type" : "number" + } + } + }, + "NetworkBandwidthGbpsRequest" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "Min" : { + "type" : "number" + }, + "Max" : { + "type" : "number" + } + } + }, + "NetworkInterfaceCountRequest" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "Min" : { + "type" : "integer" + }, + "Max" : { + "type" : "integer" + } + } + }, + "TotalLocalStorageGBRequest" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "Min" : { + "type" : "number" + }, + "Max" : { + "type" : "number" + } + } + }, + "BaselineEbsBandwidthMbpsRequest" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "Min" : { + "type" : "integer" + }, + "Max" : { + "type" : "integer" + } + } + }, + "AcceleratorCountRequest" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "Min" : { + "type" : "integer" + }, + "Max" : { + "type" : "integer" + } + } + }, + "AcceleratorTotalMemoryMiBRequest" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "Min" : { + "type" : "integer" + }, + "Max" : { + "type" : "integer" + } + } + }, + "BaselinePerformanceFactorsRequest" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "Cpu" : { + "$ref" : "#/definitions/CpuPerformanceFactorRequest" + } + } + }, + "CpuPerformanceFactorRequest" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "References" : { + "type" : "array", + "uniqueItems" : false, + "items" : { + "$ref" : "#/definitions/PerformanceFactorReferenceRequest" + } + } + } + }, + "PerformanceFactorReferenceRequest" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "InstanceFamily" : { + "type" : "string" + } + } + } + }, + "required" : [ "SpotFleetRequestConfigData" ], + "createOnlyProperties" : [ "/properties/SpotFleetRequestConfigData/AllocationStrategy", "/properties/SpotFleetRequestConfigData/IamFleetRole", "/properties/SpotFleetRequestConfigData/InstanceInterruptionBehavior", "/properties/SpotFleetRequestConfigData/InstancePoolsToUseCount", "/properties/SpotFleetRequestConfigData/LaunchSpecifications", "/properties/SpotFleetRequestConfigData/LaunchTemplateConfigs", "/properties/SpotFleetRequestConfigData/LoadBalancersConfig", "/properties/SpotFleetRequestConfigData/OnDemandAllocationStrategy", "/properties/SpotFleetRequestConfigData/OnDemandMaxTotalPrice", "/properties/SpotFleetRequestConfigData/OnDemandTargetCapacity", "/properties/SpotFleetRequestConfigData/ReplaceUnhealthyInstances", "/properties/SpotFleetRequestConfigData/SpotMaintenanceStrategies", "/properties/SpotFleetRequestConfigData/SpotMaxTotalPrice", "/properties/SpotFleetRequestConfigData/SpotPrice", "/properties/SpotFleetRequestConfigData/TagSpecifications", "/properties/SpotFleetRequestConfigData/TerminateInstancesWithExpiration", "/properties/SpotFleetRequestConfigData/Type", "/properties/SpotFleetRequestConfigData/ValidFrom", "/properties/SpotFleetRequestConfigData/ValidUntil" ], + "readOnlyProperties" : [ "/properties/Id" ], + "writeOnlyProperties" : [ "/properties/SpotFleetRequestConfigData/TagSpecifications", "/properties/SpotFleetRequestConfigData/LaunchSpecifications/*/NetworkInterfaces/*/Groups" ], + "primaryIdentifier" : [ "/properties/Id" ], + "handlers" : { + "create" : { + "permissions" : [ "iam:PassRole", "ec2:CreateTags", "ec2:RequestSpotFleet", "ec2:DescribeSpotFleetRequests", "ec2:RunInstances" ] + }, + "delete" : { + "permissions" : [ "ec2:DescribeSpotFleetRequests", "ec2:CancelSpotFleetRequests" ] + }, + "list" : { + "permissions" : [ "ec2:DescribeSpotFleetRequests" ] + }, + "read" : { + "permissions" : [ "ec2:DescribeSpotFleetRequests" ] + }, + "update" : { + "permissions" : [ "ec2:ModifySpotFleetRequest", "ec2:DescribeSpotFleetRequests" ] + } + } +} \ No newline at end of file diff --git a/tst/resources/schemas/aws-elasticloadbalancingv2-listener.json b/tst/resources/schemas/aws-elasticloadbalancingv2-listener.json new file mode 100644 index 00000000..9d405afa --- /dev/null +++ b/tst/resources/schemas/aws-elasticloadbalancingv2-listener.json @@ -0,0 +1,453 @@ +{ + "tagging" : { + "taggable" : false, + "tagOnCreate" : false, + "tagUpdatable" : false, + "cloudFormationSystemTags" : false + }, + "typeName" : "AWS::ElasticLoadBalancingV2::Listener", + "readOnlyProperties" : [ "/properties/ListenerArn" ], + "description" : "Specifies a listener for an Application Load Balancer, Network Load Balancer, or Gateway Load Balancer.", + "createOnlyProperties" : [ "/properties/LoadBalancerArn" ], + "primaryIdentifier" : [ "/properties/ListenerArn" ], + "required" : [ "LoadBalancerArn", "DefaultActions" ], + "sourceUrl" : "https://github.com/aws-cloudformation/aws-cloudformation-resource-providers-elasticloadbalancingv2.git", + "handlers" : { + "read" : { + "permissions" : [ "elasticloadbalancing:DescribeListeners", "elasticloadbalancing:DescribeListenerAttributes" ] + }, + "create" : { + "permissions" : [ "elasticloadbalancing:CreateListener", "elasticloadbalancing:DescribeListeners", "cognito-idp:DescribeUserPoolClient", "elasticloadbalancing:ModifyListenerAttributes" ] + }, + "update" : { + "permissions" : [ "elasticloadbalancing:ModifyListener", "elasticloadbalancing:DescribeListeners", "cognito-idp:DescribeUserPoolClient", "elasticloadbalancing:ModifyListenerAttributes" ] + }, + "list" : { + "permissions" : [ "elasticloadbalancing:DescribeListeners" ], + "handlerSchema" : { + "oneOf" : [ { + "required" : [ "LoadBalancerArn" ] + }, { + "required" : [ "ListenerArns" ] + } ], + "properties" : { + "LoadBalancerArn" : { + "$ref" : "resource-schema.json#/properties/LoadBalancerArn" + }, + "ListenerArns" : { + "uniqueItems" : false, + "type" : "array", + "items" : { + "$ref" : "resource-schema.json#/properties/ListenerArn" + } + } + } + } + }, + "delete" : { + "permissions" : [ "elasticloadbalancing:DeleteListener", "elasticloadbalancing:DescribeListeners" ] + } + }, + "writeOnlyProperties" : [ "/properties/DefaultActions/*/AuthenticateOidcConfig/ClientSecret" ], + "additionalProperties" : false, + "definitions" : { + "MutualAuthentication" : { + "description" : "The mutual authentication configuration information.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "IgnoreClientCertificateExpiry" : { + "description" : "Indicates whether expired client certificates are ignored.", + "type" : "boolean" + }, + "Mode" : { + "description" : "The client certificate handling method. Options are ``off``, ``passthrough`` or ``verify``. The default value is ``off``.", + "type" : "string" + }, + "TrustStoreArn" : { + "description" : "The Amazon Resource Name (ARN) of the trust store.", + "type" : "string" + }, + "AdvertiseTrustStoreCaNames" : { + "description" : "Indicates whether trust store CA certificate names are advertised.", + "type" : "string" + } + } + }, + "FixedResponseConfig" : { + "description" : "Specifies information required when returning a custom HTTP response.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "ContentType" : { + "description" : "The content type.\n Valid Values: text/plain | text/css | text/html | application/javascript | application/json", + "type" : "string" + }, + "StatusCode" : { + "description" : "The HTTP response code (2XX, 4XX, or 5XX).", + "type" : "string" + }, + "MessageBody" : { + "description" : "The message.", + "type" : "string" + } + }, + "required" : [ "StatusCode" ] + }, + "TargetGroupTuple" : { + "description" : "Information about how traffic will be distributed between multiple target groups in a forward rule.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "TargetGroupArn" : { + "relationshipRef" : { + "typeName" : "AWS::ElasticLoadBalancingV2::TargetGroup", + "propertyPath" : "/properties/TargetGroupArn" + }, + "description" : "The Amazon Resource Name (ARN) of the target group.", + "type" : "string" + }, + "Weight" : { + "description" : "The weight. The range is 0 to 999.", + "type" : "integer" + } + } + }, + "Action" : { + "description" : "Specifies an action for a listener rule.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "Order" : { + "description" : "The order for the action. This value is required for rules with multiple actions. The action with the lowest value for order is performed first.", + "type" : "integer" + }, + "TargetGroupArn" : { + "relationshipRef" : { + "typeName" : "AWS::ElasticLoadBalancingV2::TargetGroup", + "propertyPath" : "/properties/TargetGroupArn" + }, + "description" : "The Amazon Resource Name (ARN) of the target group. Specify only when ``Type`` is ``forward`` and you want to route to a single target group. To route to one or more target groups, use ``ForwardConfig`` instead.", + "type" : "string" + }, + "FixedResponseConfig" : { + "description" : "[Application Load Balancer] Information for creating an action that returns a custom HTTP response. Specify only when ``Type`` is ``fixed-response``.", + "$ref" : "#/definitions/FixedResponseConfig" + }, + "AuthenticateCognitoConfig" : { + "description" : "[HTTPS listeners] Information for using Amazon Cognito to authenticate users. Specify only when ``Type`` is ``authenticate-cognito``.", + "$ref" : "#/definitions/AuthenticateCognitoConfig" + }, + "Type" : { + "description" : "The type of action.", + "type" : "string" + }, + "RedirectConfig" : { + "description" : "[Application Load Balancer] Information for creating a redirect action. Specify only when ``Type`` is ``redirect``.", + "$ref" : "#/definitions/RedirectConfig" + }, + "ForwardConfig" : { + "description" : "Information for creating an action that distributes requests among one or more target groups. For Network Load Balancers, you can specify a single target group. Specify only when ``Type`` is ``forward``. If you specify both ``ForwardConfig`` and ``TargetGroupArn``, you can specify only one target group using ``ForwardConfig`` and it must be the same target group specified in ``TargetGroupArn``.", + "$ref" : "#/definitions/ForwardConfig" + }, + "AuthenticateOidcConfig" : { + "description" : "[HTTPS listeners] Information about an identity provider that is compliant with OpenID Connect (OIDC). Specify only when ``Type`` is ``authenticate-oidc``.", + "$ref" : "#/definitions/AuthenticateOidcConfig" + } + }, + "required" : [ "Type" ] + }, + "AuthenticateCognitoConfig" : { + "description" : "Specifies information required when integrating with Amazon Cognito to authenticate users.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "OnUnauthenticatedRequest" : { + "description" : "The behavior if the user is not authenticated. The following are possible values:\n + deny```` - Return an HTTP 401 Unauthorized error.\n + allow```` - Allow the request to be forwarded to the target.\n + authenticate```` - Redirect the request to the IdP authorization endpoint. This is the default value.", + "type" : "string" + }, + "UserPoolClientId" : { + "anyOf" : [ { + "relationshipRef" : { + "typeName" : "AWS::Cognito::UserPoolClient", + "propertyPath" : "/properties/UserPoolId" + } + }, { + "relationshipRef" : { + "typeName" : "AWS::Cognito::UserPoolClient", + "propertyPath" : "/properties/ClientId" + } + } ], + "description" : "The ID of the Amazon Cognito user pool client.", + "type" : "string" + }, + "UserPoolDomain" : { + "relationshipRef" : { + "typeName" : "AWS::Cognito::UserPoolDomain", + "propertyPath" : "/properties/Id" + }, + "description" : "The domain prefix or fully-qualified domain name of the Amazon Cognito user pool.", + "type" : "string" + }, + "SessionTimeout" : { + "description" : "The maximum duration of the authentication session, in seconds. The default is 604800 seconds (7 days).", + "type" : "string" + }, + "Scope" : { + "description" : "The set of user claims to be requested from the IdP. The default is ``openid``.\n To verify which scope values your IdP supports and how to separate multiple values, see the documentation for your IdP.", + "type" : "string" + }, + "SessionCookieName" : { + "description" : "The name of the cookie used to maintain session information. The default is AWSELBAuthSessionCookie.", + "type" : "string" + }, + "UserPoolArn" : { + "relationshipRef" : { + "typeName" : "AWS::Cognito::UserPool", + "propertyPath" : "/properties/Arn" + }, + "description" : "The Amazon Resource Name (ARN) of the Amazon Cognito user pool.", + "type" : "string" + }, + "AuthenticationRequestExtraParams" : { + "patternProperties" : { + "[a-zA-Z0-9]+" : { + "type" : "string" + } + }, + "description" : "The query parameters (up to 10) to include in the redirect request to the authorization endpoint.", + "type" : "object" + } + }, + "required" : [ "UserPoolClientId", "UserPoolDomain", "UserPoolArn" ] + }, + "RedirectConfig" : { + "description" : "Information about a redirect action.\n A URI consists of the following components: protocol://hostname:port/path?query. You must modify at least one of the following components to avoid a redirect loop: protocol, hostname, port, or path. Any components that you do not modify retain their original values.\n You can reuse URI components using the following reserved keywords:\n + #{protocol}\n + #{host}\n + #{port}\n + #{path} (the leading \"/\" is removed)\n + #{query}\n \n For example, you can change the path to \"/new/#{path}\", the hostname to \"example.#{host}\", or the query to \"#{query}&value=xyz\".", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "Path" : { + "description" : "The absolute path, starting with the leading \"/\". This component is not percent-encoded. The path can contain #{host}, #{path}, and #{port}.", + "type" : "string" + }, + "Query" : { + "description" : "The query parameters, URL-encoded when necessary, but not percent-encoded. Do not include the leading \"?\", as it is automatically added. You can specify any of the reserved keywords.", + "type" : "string" + }, + "Port" : { + "description" : "The port. You can specify a value from 1 to 65535 or #{port}.", + "type" : "string" + }, + "Host" : { + "description" : "The hostname. This component is not percent-encoded. The hostname can contain #{host}.", + "type" : "string" + }, + "Protocol" : { + "description" : "The protocol. You can specify HTTP, HTTPS, or #{protocol}. You can redirect HTTP to HTTP, HTTP to HTTPS, and HTTPS to HTTPS. You can't redirect HTTPS to HTTP.", + "type" : "string" + }, + "StatusCode" : { + "description" : "The HTTP redirect code. The redirect is either permanent (HTTP 301) or temporary (HTTP 302).", + "type" : "string" + } + }, + "required" : [ "StatusCode" ] + }, + "TargetGroupStickinessConfig" : { + "description" : "Information about the target group stickiness for a rule.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "Enabled" : { + "description" : "Indicates whether target group stickiness is enabled.", + "type" : "boolean" + }, + "DurationSeconds" : { + "description" : "The time period, in seconds, during which requests from a client should be routed to the same target group. The range is 1-604800 seconds (7 days).", + "type" : "integer" + } + } + }, + "ListenerAttribute" : { + "description" : "Information about a listener attribute.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "Value" : { + "description" : "The value of the attribute.", + "type" : "string" + }, + "Key" : { + "description" : "The name of the attribute.\n The following attribute is supported by Network Load Balancers, and Gateway Load Balancers.\n + ``tcp.idle_timeout.seconds`` - The tcp idle timeout value, in seconds. The valid range is 60-6000 seconds. The default is 350 seconds.\n \n The following attributes are only supported by Application Load Balancers.\n + ``routing.http.request.x_amzn_mtls_clientcert_serial_number.header_name`` - Enables you to modify the header name of the *X-Amzn-Mtls-Clientcert-Serial-Number* HTTP request header.\n + ``routing.http.request.x_amzn_mtls_clientcert_issuer.header_name`` - Enables you to modify the header name of the *X-Amzn-Mtls-Clientcert-Issuer* HTTP request header.\n + ``routing.http.request.x_amzn_mtls_clientcert_subject.header_name`` - Enables you to modify the header name of the *X-Amzn-Mtls-Clientcert-Subject* HTTP request header.\n + ``routing.http.request.x_amzn_mtls_clientcert_validity.header_name`` - Enables you to modify the header name of the *X-Amzn-Mtls-Clientcert-Validity* HTTP request header.\n + ``routing.http.request.x_amzn_mtls_clientcert_leaf.header_name`` - Enables you to modify the header name of the *X-Amzn-Mtls-Clientcert-Leaf* HTTP request header.\n + ``routing.http.request.x_amzn_mtls_clientcert.header_name`` - Enables you to modify the header name of the *X-Amzn-Mtls-Clientcert* HTTP request header.\n + ``routing.http.request.x_amzn_tls_version.header_name`` - Enables you to modify the header name of the *X-Amzn-Tls-Version* HTTP request header.\n + ``routing.http.request.x_amzn_tls_cipher_suite.header_name`` - Enables you to modify the header name of the *X-Amzn-Tls-Cipher-Suite* HTTP request header.\n + ``routing.http.response.server.enabled`` - Enables you to allow or remove the HTTP response server header.\n + ``routing.http.response.strict_transport_security.header_value`` - Informs browsers that the site should only be accessed using HTTPS, and that any future attempts to access it using HTTP should automatically be converted to HTTPS.\n + ``routing.http.response.access_control_allow_origin.header_value`` - Specifies which origins are allowed to access the server.\n + ``routing.http.response.access_control_allow_methods.header_value`` - Returns which HTTP methods are allowed when accessing the server from a different origin.\n + ``routing.http.response.access_control_allow_headers.header_value`` - Specifies which headers can be used during the request.\n + ``routing.http.response.access_control_allow_credentials.header_value`` - Indicates whether the browser should include credentials such as cookies or authentication when making requests.\n + ``routing.http.response.access_control_expose_headers.header_value`` - Returns which headers the browser can expose to the requesting client.\n + ``routing.http.response.access_control_max_age.header_value`` - Specifies how long the results of a preflight request can be cached, in seconds.\n + ``routing.http.response.content_security_policy.header_value`` - Specifies restrictions enforced by the browser to help minimize the risk of certain types of security threats.\n + ``routing.http.response.x_content_type_options.header_value`` - Indicates whether the MIME types advertised in the *Content-Type* headers should be followed and not be changed.\n + ``routing.http.response.x_frame_options.header_value`` - Indicates whether the browser is allowed to render a page in a *frame*, *iframe*, *embed* or *object*.", + "type" : "string" + } + } + }, + "ForwardConfig" : { + "description" : "Information for creating an action that distributes requests among one or more target groups. For Network Load Balancers, you can specify a single target group. Specify only when ``Type`` is ``forward``. If you specify both ``ForwardConfig`` and ``TargetGroupArn``, you can specify only one target group using ``ForwardConfig`` and it must be the same target group specified in ``TargetGroupArn``.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "TargetGroupStickinessConfig" : { + "description" : "Information about the target group stickiness for a rule.", + "$ref" : "#/definitions/TargetGroupStickinessConfig" + }, + "TargetGroups" : { + "uniqueItems" : true, + "description" : "Information about how traffic will be distributed between multiple target groups in a forward rule.", + "type" : "array", + "items" : { + "$ref" : "#/definitions/TargetGroupTuple" + } + } + } + }, + "AuthenticateOidcConfig" : { + "anyOf" : [ { + "required" : [ "ClientSecret" ] + }, { + "required" : [ "UseExistingClientSecret" ] + } ], + "description" : "Specifies information required using an identity provide (IdP) that is compliant with OpenID Connect (OIDC) to authenticate users.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "OnUnauthenticatedRequest" : { + "description" : "The behavior if the user is not authenticated. The following are possible values:\n + deny```` - Return an HTTP 401 Unauthorized error.\n + allow```` - Allow the request to be forwarded to the target.\n + authenticate```` - Redirect the request to the IdP authorization endpoint. This is the default value.", + "type" : "string" + }, + "TokenEndpoint" : { + "description" : "The token endpoint of the IdP. This must be a full URL, including the HTTPS protocol, the domain, and the path.", + "type" : "string" + }, + "UseExistingClientSecret" : { + "description" : "Indicates whether to use the existing client secret when modifying a rule. If you are creating a rule, you can omit this parameter or set it to false.", + "type" : "boolean" + }, + "SessionTimeout" : { + "description" : "The maximum duration of the authentication session, in seconds. The default is 604800 seconds (7 days).", + "type" : "string" + }, + "Scope" : { + "description" : "The set of user claims to be requested from the IdP. The default is ``openid``.\n To verify which scope values your IdP supports and how to separate multiple values, see the documentation for your IdP.", + "type" : "string" + }, + "Issuer" : { + "description" : "The OIDC issuer identifier of the IdP. This must be a full URL, including the HTTPS protocol, the domain, and the path.", + "type" : "string" + }, + "ClientSecret" : { + "description" : "The OAuth 2.0 client secret. This parameter is required if you are creating a rule. If you are modifying a rule, you can omit this parameter if you set ``UseExistingClientSecret`` to true.", + "type" : "string" + }, + "UserInfoEndpoint" : { + "description" : "The user info endpoint of the IdP. This must be a full URL, including the HTTPS protocol, the domain, and the path.", + "type" : "string" + }, + "ClientId" : { + "description" : "The OAuth 2.0 client identifier.", + "type" : "string" + }, + "AuthorizationEndpoint" : { + "description" : "The authorization endpoint of the IdP. This must be a full URL, including the HTTPS protocol, the domain, and the path.", + "type" : "string" + }, + "SessionCookieName" : { + "description" : "The name of the cookie used to maintain session information. The default is AWSELBAuthSessionCookie.", + "type" : "string" + }, + "AuthenticationRequestExtraParams" : { + "patternProperties" : { + "[a-zA-Z0-9]+" : { + "type" : "string" + } + }, + "description" : "The query parameters (up to 10) to include in the redirect request to the authorization endpoint.", + "type" : "object" + } + }, + "required" : [ "TokenEndpoint", "Issuer", "UserInfoEndpoint", "ClientId", "AuthorizationEndpoint" ] + }, + "Certificate" : { + "description" : "Specifies an SSL server certificate to use as the default certificate for a secure listener.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "CertificateArn" : { + "anyOf" : [ { + "relationshipRef" : { + "typeName" : "AWS::CertificateManager::Certificate", + "propertyPath" : "/properties/Id" + } + }, { + "relationshipRef" : { + "typeName" : "AWS::IAM::ServerCertificate", + "propertyPath" : "/properties/Arn" + } + } ], + "description" : "The Amazon Resource Name (ARN) of the certificate.", + "type" : "string" + } + } + } + }, + "properties" : { + "ListenerArn" : { + "description" : "", + "type" : "string" + }, + "MutualAuthentication" : { + "description" : "The mutual authentication configuration information.", + "$ref" : "#/definitions/MutualAuthentication" + }, + "ListenerAttributes" : { + "arrayType" : "AttributeList", + "uniqueItems" : true, + "description" : "The listener attributes.", + "insertionOrder" : false, + "type" : "array", + "items" : { + "$ref" : "#/definitions/ListenerAttribute" + } + }, + "AlpnPolicy" : { + "description" : "[TLS listener] The name of the Application-Layer Protocol Negotiation (ALPN) policy.", + "type" : "array", + "items" : { + "type" : "string" + } + }, + "SslPolicy" : { + "description" : "[HTTPS and TLS listeners] The security policy that defines which protocols and ciphers are supported. For more information, see [Security policies](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/describe-ssl-policies.html) in the *Application Load Balancers Guide* and [Security policies](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/describe-ssl-policies.html) in the *Network Load Balancers Guide*.\n Updating the security policy can result in interruptions if the load balancer is handling a high volume of traffic. To decrease the possibility of an interruption if your load balancer is handling a high volume of traffic, create an additional load balancer or request an LCU reservation.", + "type" : "string" + }, + "LoadBalancerArn" : { + "description" : "The Amazon Resource Name (ARN) of the load balancer.", + "type" : "string" + }, + "DefaultActions" : { + "uniqueItems" : true, + "description" : "The actions for the default rule. You cannot define a condition for a default rule.\n To create additional rules for an Application Load Balancer, use [AWS::ElasticLoadBalancingV2::ListenerRule](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-elasticloadbalancingv2-listenerrule.html).", + "type" : "array", + "items" : { + "$ref" : "#/definitions/Action" + } + }, + "Port" : { + "description" : "The port on which the load balancer is listening. You can't specify a port for a Gateway Load Balancer.", + "type" : "integer" + }, + "Certificates" : { + "uniqueItems" : true, + "description" : "The default SSL server certificate for a secure listener. You must provide exactly one certificate if the listener protocol is HTTPS or TLS.\n To create a certificate list for a secure listener, use [AWS::ElasticLoadBalancingV2::ListenerCertificate](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-elasticloadbalancingv2-listenercertificate.html).", + "type" : "array", + "items" : { + "$ref" : "#/definitions/Certificate" + } + }, + "Protocol" : { + "description" : "The protocol for connections from clients to the load balancer. For Application Load Balancers, the supported protocols are HTTP and HTTPS. For Network Load Balancers, the supported protocols are TCP, TLS, UDP, and TCP_UDP. You can’t specify the UDP or TCP_UDP protocol if dual-stack mode is enabled. You can't specify a protocol for a Gateway Load Balancer.", + "type" : "string" + } + } +} \ No newline at end of file diff --git a/tst/resources/schemas/aws-elasticloadbalancingv2-listenerrule.json b/tst/resources/schemas/aws-elasticloadbalancingv2-listenerrule.json new file mode 100644 index 00000000..fe70b2ab --- /dev/null +++ b/tst/resources/schemas/aws-elasticloadbalancingv2-listenerrule.json @@ -0,0 +1,506 @@ +{ + "typeName" : "AWS::ElasticLoadBalancingV2::ListenerRule", + "description" : "Specifies a listener rule. The listener must be associated with an Application Load Balancer. Each rule consists of a priority, one or more actions, and one or more conditions.\n For more information, see [Quotas for your Application Load Balancers](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-limits.html) in the *User Guide for Application Load Balancers*.", + "sourceUrl" : "https://github.com/aws-cloudformation/aws-cloudformation-resource-providers-elasticloadbalancingv2", + "additionalProperties" : false, + "properties" : { + "ListenerArn" : { + "type" : "string", + "description" : "The Amazon Resource Name (ARN) of the listener." + }, + "RuleArn" : { + "type" : "string", + "description" : "" + }, + "Actions" : { + "type" : "array", + "uniqueItems" : true, + "insertionOrder" : false, + "items" : { + "$ref" : "#/definitions/Action" + }, + "description" : "The actions.\n The rule must include exactly one of the following types of actions: ``forward``, ``fixed-response``, or ``redirect``, and it must be the last action to be performed. If the rule is for an HTTPS listener, it can also optionally include an authentication action." + }, + "Priority" : { + "type" : "integer", + "description" : "The rule priority. A listener can't have multiple rules with the same priority.\n If you try to reorder rules by updating their priorities, do not specify a new priority if an existing rule already uses this priority, as this can cause an error. If you need to reuse a priority with a different rule, you must remove it as a priority first, and then specify it in a subsequent update." + }, + "Conditions" : { + "type" : "array", + "uniqueItems" : true, + "insertionOrder" : false, + "items" : { + "$ref" : "#/definitions/RuleCondition" + }, + "description" : "The conditions.\n The rule can optionally include up to one of each of the following conditions: ``http-request-method``, ``host-header``, ``path-pattern``, and ``source-ip``. A rule can also optionally include one or more of each of the following conditions: ``http-header`` and ``query-string``." + }, + "IsDefault" : { + "type" : "boolean", + "description" : "" + } + }, + "definitions" : { + "TargetGroupTuple" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "TargetGroupArn" : { + "type" : "string", + "description" : "The Amazon Resource Name (ARN) of the target group." + }, + "Weight" : { + "type" : "integer", + "description" : "The weight. The range is 0 to 999." + } + }, + "description" : "Information about how traffic will be distributed between multiple target groups in a forward rule." + }, + "Action" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "Order" : { + "type" : "integer", + "description" : "The order for the action. This value is required for rules with multiple actions. The action with the lowest value for order is performed first." + }, + "TargetGroupArn" : { + "type" : "string", + "description" : "The Amazon Resource Name (ARN) of the target group. Specify only when ``Type`` is ``forward`` and you want to route to a single target group. To route to one or more target groups, use ``ForwardConfig`` instead." + }, + "FixedResponseConfig" : { + "$ref" : "#/definitions/FixedResponseConfig", + "description" : "[Application Load Balancer] Information for creating an action that returns a custom HTTP response. Specify only when ``Type`` is ``fixed-response``." + }, + "AuthenticateCognitoConfig" : { + "$ref" : "#/definitions/AuthenticateCognitoConfig", + "description" : "[HTTPS listeners] Information for using Amazon Cognito to authenticate users. Specify only when ``Type`` is ``authenticate-cognito``." + }, + "Type" : { + "type" : "string", + "description" : "The type of action." + }, + "RedirectConfig" : { + "$ref" : "#/definitions/RedirectConfig", + "description" : "[Application Load Balancer] Information for creating a redirect action. Specify only when ``Type`` is ``redirect``." + }, + "ForwardConfig" : { + "$ref" : "#/definitions/ForwardConfig", + "description" : "Information for creating an action that distributes requests among one or more target groups. For Network Load Balancers, you can specify a single target group. Specify only when ``Type`` is ``forward``. If you specify both ``ForwardConfig`` and ``TargetGroupArn``, you can specify only one target group using ``ForwardConfig`` and it must be the same target group specified in ``TargetGroupArn``." + }, + "AuthenticateOidcConfig" : { + "$ref" : "#/definitions/AuthenticateOidcConfig", + "description" : "[HTTPS listeners] Information about an identity provider that is compliant with OpenID Connect (OIDC). Specify only when ``Type`` is ``authenticate-oidc``." + } + }, + "required" : [ "Type" ], + "description" : "Specifies an action for a listener rule." + }, + "RuleCondition" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "Field" : { + "type" : "string", + "description" : "The field in the HTTP request. The following are the possible values:\n + ``http-header`` \n + ``http-request-method`` \n + ``host-header`` \n + ``path-pattern`` \n + ``query-string`` \n + ``source-ip``" + }, + "Values" : { + "type" : "array", + "uniqueItems" : true, + "insertionOrder" : false, + "items" : { + "type" : "string" + }, + "description" : "The condition value. Specify only when ``Field`` is ``host-header`` or ``path-pattern``. Alternatively, to specify multiple host names or multiple path patterns, use ``HostHeaderConfig`` or ``PathPatternConfig``.\n If ``Field`` is ``host-header`` and you're not using ``HostHeaderConfig``, you can specify a single host name (for example, my.example.com). A host name is case insensitive, can be up to 128 characters in length, and can contain any of the following characters.\n + A-Z, a-z, 0-9\n + - .\n + * (matches 0 or more characters)\n + ? (matches exactly 1 character)\n \n If ``Field`` is ``path-pattern`` and you're not using ``PathPatternConfig``, you can specify a single path pattern (for example, /img/*). A path pattern is case-sensitive, can be up to 128 characters in length, and can contain any of the following characters.\n + A-Z, a-z, 0-9\n + _ - . $ / ~ \" ' @ : +\n + & (using &)\n + * (matches 0 or more characters)\n + ? (matches exactly 1 character)" + }, + "HttpRequestMethodConfig" : { + "$ref" : "#/definitions/HttpRequestMethodConfig", + "description" : "Information for an HTTP method condition. Specify only when ``Field`` is ``http-request-method``." + }, + "PathPatternConfig" : { + "$ref" : "#/definitions/PathPatternConfig", + "description" : "Information for a path pattern condition. Specify only when ``Field`` is ``path-pattern``." + }, + "HttpHeaderConfig" : { + "$ref" : "#/definitions/HttpHeaderConfig", + "description" : "Information for an HTTP header condition. Specify only when ``Field`` is ``http-header``." + }, + "SourceIpConfig" : { + "$ref" : "#/definitions/SourceIpConfig", + "description" : "Information for a source IP condition. Specify only when ``Field`` is ``source-ip``." + }, + "HostHeaderConfig" : { + "$ref" : "#/definitions/HostHeaderConfig", + "description" : "Information for a host header condition. Specify only when ``Field`` is ``host-header``." + }, + "QueryStringConfig" : { + "$ref" : "#/definitions/QueryStringConfig", + "description" : "Information for a query string condition. Specify only when ``Field`` is ``query-string``." + } + }, + "description" : "Specifies a condition for a listener rule." + }, + "QueryStringConfig" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "Values" : { + "type" : "array", + "uniqueItems" : true, + "insertionOrder" : false, + "items" : { + "$ref" : "#/definitions/QueryStringKeyValue" + }, + "description" : "The key/value pairs or values to find in the query string. The maximum size of each string is 128 characters. The comparison is case insensitive. The following wildcard characters are supported: * (matches 0 or more characters) and ? (matches exactly 1 character). To search for a literal '*' or '?' character in a query string, you must escape these characters in ``Values`` using a '\\' character.\n If you specify multiple key/value pairs or values, the condition is satisfied if one of them is found in the query string." + } + }, + "description" : "Information about a query string condition.\n The query string component of a URI starts after the first '?' character and is terminated by either a '#' character or the end of the URI. A typical query string contains key/value pairs separated by '&' characters. The allowed characters are specified by RFC 3986. Any character can be percentage encoded." + }, + "TargetGroupStickinessConfig" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "Enabled" : { + "type" : "boolean", + "description" : "Indicates whether target group stickiness is enabled." + }, + "DurationSeconds" : { + "type" : "integer", + "description" : "The time period, in seconds, during which requests from a client should be routed to the same target group. The range is 1-604800 seconds (7 days)." + } + }, + "description" : "Information about the target group stickiness for a rule." + }, + "PathPatternConfig" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "Values" : { + "type" : "array", + "uniqueItems" : true, + "insertionOrder" : false, + "items" : { + "type" : "string" + }, + "description" : "The path patterns to compare against the request URL. The maximum size of each string is 128 characters. The comparison is case sensitive. The following wildcard characters are supported: * (matches 0 or more characters) and ? (matches exactly 1 character).\n If you specify multiple strings, the condition is satisfied if one of them matches the request URL. The path pattern is compared only to the path of the URL, not to its query string." + } + }, + "description" : "Information about a path pattern condition." + }, + "FixedResponseConfig" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "ContentType" : { + "type" : "string", + "description" : "The content type.\n Valid Values: text/plain | text/css | text/html | application/javascript | application/json" + }, + "StatusCode" : { + "type" : "string", + "description" : "The HTTP response code (2XX, 4XX, or 5XX)." + }, + "MessageBody" : { + "type" : "string", + "description" : "The message." + } + }, + "required" : [ "StatusCode" ], + "description" : "Specifies information required when returning a custom HTTP response." + }, + "HttpHeaderConfig" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "Values" : { + "type" : "array", + "uniqueItems" : true, + "insertionOrder" : false, + "items" : { + "type" : "string" + }, + "description" : "The strings to compare against the value of the HTTP header. The maximum size of each string is 128 characters. The comparison strings are case insensitive. The following wildcard characters are supported: * (matches 0 or more characters) and ? (matches exactly 1 character).\n If the same header appears multiple times in the request, we search them in order until a match is found.\n If you specify multiple strings, the condition is satisfied if one of the strings matches the value of the HTTP header. To require that all of the strings are a match, create one condition per string." + }, + "HttpHeaderName" : { + "type" : "string", + "description" : "The name of the HTTP header field. The maximum size is 40 characters. The header name is case insensitive. The allowed characters are specified by RFC 7230. Wildcards are not supported." + } + }, + "description" : "Information about an HTTP header condition.\n There is a set of standard HTTP header fields. You can also define custom HTTP header fields." + }, + "AuthenticateCognitoConfig" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "OnUnauthenticatedRequest" : { + "type" : "string", + "description" : "The behavior if the user is not authenticated. The following are possible values:\n + deny```` - Return an HTTP 401 Unauthorized error.\n + allow```` - Allow the request to be forwarded to the target.\n + authenticate```` - Redirect the request to the IdP authorization endpoint. This is the default value." + }, + "UserPoolClientId" : { + "type" : "string", + "description" : "The ID of the Amazon Cognito user pool client." + }, + "UserPoolDomain" : { + "type" : "string", + "description" : "The domain prefix or fully-qualified domain name of the Amazon Cognito user pool." + }, + "SessionTimeout" : { + "type" : "integer", + "description" : "The maximum duration of the authentication session, in seconds. The default is 604800 seconds (7 days)." + }, + "Scope" : { + "type" : "string", + "description" : "The set of user claims to be requested from the IdP. The default is ``openid``.\n To verify which scope values your IdP supports and how to separate multiple values, see the documentation for your IdP." + }, + "SessionCookieName" : { + "type" : "string", + "description" : "The name of the cookie used to maintain session information. The default is AWSELBAuthSessionCookie." + }, + "UserPoolArn" : { + "type" : "string", + "description" : "The Amazon Resource Name (ARN) of the Amazon Cognito user pool." + }, + "AuthenticationRequestExtraParams" : { + "type" : "object", + "additionalProperties" : false, + "patternProperties" : { + "[a-zA-Z0-9]+" : { + "type" : "string" + } + }, + "description" : "The query parameters (up to 10) to include in the redirect request to the authorization endpoint." + } + }, + "required" : [ "UserPoolClientId", "UserPoolDomain", "UserPoolArn" ], + "description" : "Specifies information required when integrating with Amazon Cognito to authenticate users." + }, + "RedirectConfig" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "Path" : { + "type" : "string", + "description" : "The absolute path, starting with the leading \"/\". This component is not percent-encoded. The path can contain #{host}, #{path}, and #{port}." + }, + "Query" : { + "type" : "string", + "description" : "The query parameters, URL-encoded when necessary, but not percent-encoded. Do not include the leading \"?\", as it is automatically added. You can specify any of the reserved keywords." + }, + "Port" : { + "type" : "string", + "description" : "The port. You can specify a value from 1 to 65535 or #{port}." + }, + "Host" : { + "type" : "string", + "description" : "The hostname. This component is not percent-encoded. The hostname can contain #{host}." + }, + "Protocol" : { + "type" : "string", + "description" : "The protocol. You can specify HTTP, HTTPS, or #{protocol}. You can redirect HTTP to HTTP, HTTP to HTTPS, and HTTPS to HTTPS. You can't redirect HTTPS to HTTP." + }, + "StatusCode" : { + "type" : "string", + "description" : "The HTTP redirect code. The redirect is either permanent (HTTP 301) or temporary (HTTP 302)." + } + }, + "required" : [ "StatusCode" ], + "description" : "Information about a redirect action.\n A URI consists of the following components: protocol://hostname:port/path?query. You must modify at least one of the following components to avoid a redirect loop: protocol, hostname, port, or path. Any components that you do not modify retain their original values.\n You can reuse URI components using the following reserved keywords:\n + #{protocol}\n + #{host}\n + #{port}\n + #{path} (the leading \"/\" is removed)\n + #{query}\n \n For example, you can change the path to \"/new/#{path}\", the hostname to \"example.#{host}\", or the query to \"#{query}&value=xyz\"." + }, + "QueryStringKeyValue" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "Value" : { + "type" : "string", + "description" : "The value." + }, + "Key" : { + "type" : "string", + "description" : "The key. You can omit the key." + } + }, + "description" : "Information about a key/value pair." + }, + "ForwardConfig" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "TargetGroupStickinessConfig" : { + "$ref" : "#/definitions/TargetGroupStickinessConfig", + "description" : "Information about the target group stickiness for a rule." + }, + "TargetGroups" : { + "type" : "array", + "uniqueItems" : true, + "insertionOrder" : false, + "items" : { + "$ref" : "#/definitions/TargetGroupTuple" + }, + "description" : "Information about how traffic will be distributed between multiple target groups in a forward rule." + } + }, + "description" : "Information for creating an action that distributes requests among one or more target groups. For Network Load Balancers, you can specify a single target group. Specify only when ``Type`` is ``forward``. If you specify both ``ForwardConfig`` and ``TargetGroupArn``, you can specify only one target group using ``ForwardConfig`` and it must be the same target group specified in ``TargetGroupArn``." + }, + "HostHeaderConfig" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "Values" : { + "type" : "array", + "uniqueItems" : true, + "insertionOrder" : false, + "items" : { + "type" : "string" + }, + "description" : "The host names. The maximum size of each name is 128 characters. The comparison is case insensitive. The following wildcard characters are supported: * (matches 0 or more characters) and ? (matches exactly 1 character).\n If you specify multiple strings, the condition is satisfied if one of the strings matches the host name." + } + }, + "description" : "Information about a host header condition." + }, + "HttpRequestMethodConfig" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "Values" : { + "type" : "array", + "uniqueItems" : true, + "insertionOrder" : false, + "items" : { + "type" : "string" + }, + "description" : "The name of the request method. The maximum size is 40 characters. The allowed characters are A-Z, hyphen (-), and underscore (_). The comparison is case sensitive. Wildcards are not supported; therefore, the method name must be an exact match.\n If you specify multiple strings, the condition is satisfied if one of the strings matches the HTTP request method. We recommend that you route GET and HEAD requests in the same way, because the response to a HEAD request may be cached." + } + }, + "description" : "Information about an HTTP method condition.\n HTTP defines a set of request methods, also referred to as HTTP verbs. For more information, see the [HTTP Method Registry](https://docs.aws.amazon.com/https://www.iana.org/assignments/http-methods/http-methods.xhtml). You can also define custom HTTP methods." + }, + "AuthenticateOidcConfig" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "OnUnauthenticatedRequest" : { + "type" : "string", + "description" : "The behavior if the user is not authenticated. The following are possible values:\n + deny```` - Return an HTTP 401 Unauthorized error.\n + allow```` - Allow the request to be forwarded to the target.\n + authenticate```` - Redirect the request to the IdP authorization endpoint. This is the default value." + }, + "TokenEndpoint" : { + "type" : "string", + "description" : "The token endpoint of the IdP. This must be a full URL, including the HTTPS protocol, the domain, and the path." + }, + "SessionTimeout" : { + "type" : "integer", + "description" : "The maximum duration of the authentication session, in seconds. The default is 604800 seconds (7 days)." + }, + "Scope" : { + "type" : "string", + "description" : "The set of user claims to be requested from the IdP. The default is ``openid``.\n To verify which scope values your IdP supports and how to separate multiple values, see the documentation for your IdP." + }, + "Issuer" : { + "type" : "string", + "description" : "The OIDC issuer identifier of the IdP. This must be a full URL, including the HTTPS protocol, the domain, and the path." + }, + "ClientSecret" : { + "type" : "string", + "description" : "The OAuth 2.0 client secret. This parameter is required if you are creating a rule. If you are modifying a rule, you can omit this parameter if you set ``UseExistingClientSecret`` to true." + }, + "UserInfoEndpoint" : { + "type" : "string", + "description" : "The user info endpoint of the IdP. This must be a full URL, including the HTTPS protocol, the domain, and the path." + }, + "ClientId" : { + "type" : "string", + "description" : "The OAuth 2.0 client identifier." + }, + "AuthorizationEndpoint" : { + "type" : "string", + "description" : "The authorization endpoint of the IdP. This must be a full URL, including the HTTPS protocol, the domain, and the path." + }, + "SessionCookieName" : { + "type" : "string", + "description" : "The name of the cookie used to maintain session information. The default is AWSELBAuthSessionCookie." + }, + "UseExistingClientSecret" : { + "type" : "boolean", + "description" : "Indicates whether to use the existing client secret when modifying a rule. If you are creating a rule, you can omit this parameter or set it to false." + }, + "AuthenticationRequestExtraParams" : { + "type" : "object", + "additionalProperties" : false, + "patternProperties" : { + "[a-zA-Z0-9]+" : { + "type" : "string" + } + }, + "description" : "The query parameters (up to 10) to include in the redirect request to the authorization endpoint." + } + }, + "required" : [ "TokenEndpoint", "Issuer", "UserInfoEndpoint", "ClientId", "AuthorizationEndpoint" ], + "anyOf" : [ { + "required" : [ "ClientSecret" ] + }, { + "required" : [ "UseExistingClientSecret" ] + } ], + "description" : "Specifies information required using an identity provide (IdP) that is compliant with OpenID Connect (OIDC) to authenticate users." + }, + "SourceIpConfig" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "Values" : { + "type" : "array", + "uniqueItems" : true, + "insertionOrder" : false, + "items" : { + "type" : "string" + }, + "description" : "The source IP addresses, in CIDR format. You can use both IPv4 and IPv6 addresses. Wildcards are not supported.\n If you specify multiple addresses, the condition is satisfied if the source IP address of the request matches one of the CIDR blocks. This condition is not satisfied by the addresses in the X-Forwarded-For header." + } + }, + "description" : "Information about a source IP condition.\n You can use this condition to route based on the IP address of the source that connects to the load balancer. If a client is behind a proxy, this is the IP address of the proxy not the IP address of the client." + } + }, + "required" : [ "Actions", "Priority", "Conditions" ], + "createOnlyProperties" : [ "/properties/ListenerArn" ], + "primaryIdentifier" : [ "/properties/RuleArn" ], + "readOnlyProperties" : [ "/properties/RuleArn", "/properties/IsDefault" ], + "writeOnlyProperties" : [ "/properties/Actions/*/AuthenticateOidcConfig/ClientSecret", "/properties/ListenerArn" ], + "tagging" : { + "taggable" : false, + "tagOnCreate" : false, + "tagUpdatable" : false, + "cloudFormationSystemTags" : false + }, + "handlers" : { + "create" : { + "permissions" : [ "elasticloadbalancing:CreateRule", "elasticloadbalancing:DescribeRules", "cognito-idp:DescribeUserPoolClient" ] + }, + "delete" : { + "permissions" : [ "elasticloadbalancing:DeleteRule", "elasticloadbalancing:DescribeRules" ] + }, + "list" : { + "handlerSchema" : { + "properties" : { + "ListenerArn" : { + "$ref" : "resource-schema.json#/properties/ListenerArn" + }, + "RuleArns" : { + "type" : "array", + "uniqueItems" : false, + "insertionOrder" : false, + "items" : { + "$ref" : "resource-schema.json#/properties/RuleArn" + } + } + }, + "oneOf" : [ { + "required" : [ "ListenerArn" ] + }, { + "required" : [ "RuleArns" ] + } ] + }, + "permissions" : [ "elasticloadbalancing:DescribeRules" ] + }, + "read" : { + "permissions" : [ "elasticloadbalancing:DescribeRules" ] + }, + "update" : { + "permissions" : [ "elasticloadbalancing:ModifyRule", "elasticloadbalancing:SetRulePriorities", "elasticloadbalancing:DescribeRules" ] + } + } +} \ No newline at end of file diff --git a/tst/resources/schemas/aws-securitylake-subscribernotification.json b/tst/resources/schemas/aws-securitylake-subscribernotification.json new file mode 100644 index 00000000..e2365edd --- /dev/null +++ b/tst/resources/schemas/aws-securitylake-subscribernotification.json @@ -0,0 +1,100 @@ +{ + "typeName" : "AWS::SecurityLake::SubscriberNotification", + "description" : "Resource Type definition for AWS::SecurityLake::SubscriberNotification", + "sourceUrl" : "https://github.com/aws-cloudformation/aws-cloudformation-resource-providers-securitylake.git", + "definitions" : { + "HttpsNotificationConfiguration" : { + "type" : "object", + "properties" : { + "AuthorizationApiKeyName" : { + "type" : "string", + "description" : "The key name for the notification subscription." + }, + "AuthorizationApiKeyValue" : { + "type" : "string", + "description" : "The key value for the notification subscription." + }, + "Endpoint" : { + "type" : "string", + "pattern" : "^https?://.+$", + "description" : "The subscription endpoint in Security Lake." + }, + "HttpMethod" : { + "type" : "string", + "enum" : [ "POST", "PUT" ], + "description" : "The HTTPS method used for the notification subscription." + }, + "TargetRoleArn" : { + "type" : "string", + "pattern" : "^arn:.*$", + "description" : "The Amazon Resource Name (ARN) of the EventBridge API destinations IAM role that you created." + } + }, + "description" : "The configuration for HTTPS subscriber notification.", + "additionalProperties" : false, + "required" : [ "Endpoint", "TargetRoleArn" ] + }, + "SqsNotificationConfiguration" : { + "type" : "object", + "description" : "The configurations for SQS subscriber notification. The members of this structure are context-dependent." + }, + "NotificationConfiguration" : { + "type" : "object", + "properties" : { + "HttpsNotificationConfiguration" : { + "$ref" : "#/definitions/HttpsNotificationConfiguration" + }, + "SqsNotificationConfiguration" : { + "$ref" : "#/definitions/SqsNotificationConfiguration" + } + }, + "additionalProperties" : false, + "oneOf" : [ { + "required" : [ "HttpsNotificationConfiguration" ] + }, { + "required" : [ "SqsNotificationConfiguration" ] + } ] + } + }, + "properties" : { + "NotificationConfiguration" : { + "$ref" : "#/definitions/NotificationConfiguration" + }, + "SubscriberArn" : { + "description" : "The ARN for the subscriber", + "type" : "string", + "pattern" : "^arn:.*$" + }, + "SubscriberEndpoint" : { + "description" : "The endpoint the subscriber should listen to for notifications", + "type" : "string" + } + }, + "tagging" : { + "taggable" : false + }, + "additionalProperties" : false, + "required" : [ "SubscriberArn", "NotificationConfiguration" ], + "primaryIdentifier" : [ "/properties/SubscriberArn" ], + "readOnlyProperties" : [ "/properties/SubscriberEndpoint" ], + "createOnlyProperties" : [ "/properties/SubscriberArn" ], + "writeOnlyProperties" : [ "/properties/NotificationConfiguration/HttpsNotificationConfiguration/AuthorizationApiKeyName", "/properties/NotificationConfiguration/HttpsNotificationConfiguration/AuthorizationApiKeyValue", "/properties/NotificationConfiguration/HttpsNotificationConfiguration/Endpoint", "/properties/NotificationConfiguration/HttpsNotificationConfiguration/HttpMethod", "/properties/NotificationConfiguration/HttpsNotificationConfiguration/TargetRoleArn" ], + "replacementStrategy" : "delete_then_create", + "handlers" : { + "create" : { + "permissions" : [ "securitylake:CreateDataLake", "securitylake:CreateSubscriber", "securitylake:CreateSubscriberNotification", "securitylake:GetSubscriber", "iam:CreateServiceLinkedRole", "iam:PutRolePolicy", "iam:DeleteRolePolicy", "iam:PassRole", "s3:PutBucketNotification", "s3:GetBucketNotification", "events:CreateApiDestination", "events:CreateConnection", "events:CreateRule", "events:UpdateConnection", "events:DeleteConnection", "events:UpdateApiDestination", "events:DeleteApiDestination", "events:ListApiDestinations", "events:ListConnections", "events:PutRule", "events:DescribeRule", "events:DeleteRule", "events:PutTargets", "events:RemoveTargets", "events:ListTargetsByRule", "secretsmanager:CreateSecret", "sqs:CreateQueue", "sqs:GetQueueAttributes", "sqs:GetQueueUrl", "sqs:SetQueueAttributes" ] + }, + "read" : { + "permissions" : [ "securitylake:GetSubscriber" ] + }, + "update" : { + "permissions" : [ "securitylake:UpdateSubscriberNotification", "securitylake:GetSubscriber", "iam:CreateServiceLinkedRole", "iam:PutRolePolicy", "iam:DeleteRolePolicy", "iam:PassRole", "events:CreateApiDestination", "events:CreateConnection", "events:UpdateConnection", "events:DeleteConnection", "events:UpdateApiDestination", "events:DeleteApiDestination", "events:DeleteRule", "events:ListApiDestinations", "events:ListConnections", "events:PutRule", "events:DescribeRule", "events:DeleteRule", "events:PutTargets", "events:RemoveTargets", "events:ListTargetsByRule", "secretsmanager:CreateSecret", "s3:GetBucketNotificationConfiguration", "s3:PutBucketNotificationConfiguration", "s3:PutBucketNotification", "s3:GetBucketNotification", "sqs:CreateQueue", "sqs:DeleteQueue", "sqs:GetQueueAttributes", "sqs:SetQueueAttributes" ] + }, + "delete" : { + "permissions" : [ "securitylake:DeleteSubscriberNotification", "securitylake:GetSubscriber", "iam:DeleteRole", "iam:DeleteRolePolicy", "events:DeleteApiDestination", "events:DeleteConnection", "events:DeleteRule", "events:ListTargetsByRule", "events:DescribeRule", "events:RemoveTargets", "sqs:DeleteQueue" ] + }, + "list" : { + "permissions" : [ "securitylake:ListSubscribers" ] + } + } +} \ No newline at end of file diff --git a/tst/resources/schemas/aws-synthetics-canary.json b/tst/resources/schemas/aws-synthetics-canary.json new file mode 100644 index 00000000..d9c88f02 --- /dev/null +++ b/tst/resources/schemas/aws-synthetics-canary.json @@ -0,0 +1,328 @@ +{ + "typeName" : "AWS::Synthetics::Canary", + "description" : "Resource Type definition for AWS::Synthetics::Canary", + "sourceUrl" : "https://github.com/aws-cloudformation/aws-cloudformation-resource-providers-synthetics", + "properties" : { + "Name" : { + "description" : "Name of the canary.", + "type" : "string", + "pattern" : "^[0-9a-z_\\-]{1,255}$" + }, + "Id" : { + "description" : "Id of the canary", + "type" : "string" + }, + "State" : { + "description" : "State of the canary", + "type" : "string" + }, + "Code" : { + "description" : "Provide the canary script source", + "$ref" : "#/definitions/Code" + }, + "ArtifactS3Location" : { + "description" : "Provide the s3 bucket output location for test results", + "type" : "string", + "pattern" : "^(s3|S3)://" + }, + "ArtifactConfig" : { + "description" : "Provide artifact configuration", + "$ref" : "#/definitions/ArtifactConfig" + }, + "Schedule" : { + "description" : "Frequency to run your canaries", + "$ref" : "#/definitions/Schedule" + }, + "ExecutionRoleArn" : { + "description" : "Lambda Execution role used to run your canaries", + "type" : "string" + }, + "RuntimeVersion" : { + "description" : "Runtime version of Synthetics Library", + "type" : "string" + }, + "SuccessRetentionPeriod" : { + "description" : "Retention period of successful canary runs represented in number of days", + "type" : "integer" + }, + "FailureRetentionPeriod" : { + "description" : "Retention period of failed canary runs represented in number of days", + "type" : "integer" + }, + "Tags" : { + "type" : "array", + "uniqueItems" : false, + "items" : { + "$ref" : "#/definitions/Tag" + } + }, + "VPCConfig" : { + "description" : "Provide VPC Configuration if enabled.", + "$ref" : "#/definitions/VPCConfig" + }, + "RunConfig" : { + "description" : "Provide canary run configuration", + "$ref" : "#/definitions/RunConfig" + }, + "StartCanaryAfterCreation" : { + "description" : "Runs canary if set to True. Default is False", + "type" : "boolean" + }, + "VisualReference" : { + "description" : "Visual reference configuration for visual testing", + "$ref" : "#/definitions/VisualReference" + }, + "DeleteLambdaResourcesOnCanaryDeletion" : { + "description" : "Deletes associated lambda resources created by Synthetics if set to True. Default is False", + "type" : "boolean" + }, + "ResourcesToReplicateTags" : { + "type" : "array", + "uniqueItems" : true, + "description" : "List of resources which canary tags should be replicated to.", + "items" : { + "$ref" : "#/definitions/ResourceToTag" + } + }, + "ProvisionedResourceCleanup" : { + "description" : "Setting to control if provisioned resources created by Synthetics are deleted alongside the canary. Default is AUTOMATIC.", + "type" : "string", + "enum" : [ "AUTOMATIC", "OFF" ] + }, + "DryRunAndUpdate" : { + "description" : "Setting to control if UpdateCanary will perform a DryRun and validate it is PASSING before performing the Update. Default is FALSE.", + "type" : "boolean" + } + }, + "definitions" : { + "Schedule" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "Expression" : { + "type" : "string" + }, + "DurationInSeconds" : { + "type" : "string" + }, + "RetryConfig" : { + "$ref" : "#/definitions/RetryConfig", + "description" : "Provide canary auto retry configuration" + } + }, + "required" : [ "Expression" ] + }, + "Code" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "S3Bucket" : { + "type" : "string" + }, + "S3Key" : { + "type" : "string" + }, + "S3ObjectVersion" : { + "type" : "string" + }, + "Script" : { + "type" : "string" + }, + "Handler" : { + "type" : "string" + }, + "SourceLocationArn" : { + "type" : "string" + } + }, + "required" : [ "Handler" ], + "oneOf" : [ { + "required" : [ "S3Bucket", "S3Key" ] + }, { + "required" : [ "Script" ] + } ] + }, + "Tag" : { + "description" : "A key-value pair to associate with a resource.", + "additionalProperties" : false, + "type" : "object", + "properties" : { + "Key" : { + "type" : "string", + "description" : "The key name of the tag. You can specify a value that is 1 to 127 Unicode characters in length and cannot be prefixed with aws:. You can use any of the following characters: the set of Unicode letters, digits, whitespace, _, ., /, =, +, and -. ", + "minLength" : 1, + "maxLength" : 128 + }, + "Value" : { + "type" : "string", + "description" : "The value for the tag. You can specify a value that is 1 to 255 Unicode characters in length and cannot be prefixed with aws:. You can use any of the following characters: the set of Unicode letters, digits, whitespace, _, ., /, =, +, and -. ", + "minLength" : 0, + "maxLength" : 256 + } + }, + "required" : [ "Value", "Key" ] + }, + "VPCConfig" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "VpcId" : { + "type" : "string" + }, + "SubnetIds" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "SecurityGroupIds" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "Ipv6AllowedForDualStack" : { + "description" : "Allow outbound IPv6 traffic on VPC canaries that are connected to dual-stack subnets if set to true", + "type" : "boolean" + } + }, + "required" : [ "SubnetIds", "SecurityGroupIds" ] + }, + "RunConfig" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "TimeoutInSeconds" : { + "description" : "Provide maximum canary timeout per run in seconds", + "type" : "integer" + }, + "MemoryInMB" : { + "description" : "Provide maximum memory available for canary in MB", + "type" : "integer" + }, + "ActiveTracing" : { + "description" : "Enable active tracing if set to true", + "type" : "boolean" + }, + "EnvironmentVariables" : { + "type" : "object", + "additionalProperties" : false, + "description" : "Environment variable key-value pairs.", + "patternProperties" : { + "[a-zA-Z][a-zA-Z0-9_]+" : { + "type" : "string" + } + } + } + } + }, + "VisualReference" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "BaseCanaryRunId" : { + "type" : "string", + "description" : "Canary run id to be used as base reference for visual testing" + }, + "BaseScreenshots" : { + "type" : "array", + "description" : "List of screenshots used as base reference for visual testing", + "items" : { + "$ref" : "#/definitions/BaseScreenshot" + } + } + }, + "required" : [ "BaseCanaryRunId" ] + }, + "BaseScreenshot" : { + "type" : "object", + "properties" : { + "ScreenshotName" : { + "type" : "string", + "description" : "Name of the screenshot to be used as base reference for visual testing" + }, + "IgnoreCoordinates" : { + "type" : "array", + "description" : "List of coordinates of rectangles to be ignored during visual testing", + "items" : { + "type" : "string", + "description" : "Coordinates of a rectangle to be ignored during visual testing" + } + } + }, + "required" : [ "ScreenshotName" ] + }, + "ArtifactConfig" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "S3Encryption" : { + "$ref" : "#/definitions/S3Encryption", + "description" : "Encryption configuration for uploading artifacts to S3" + } + } + }, + "S3Encryption" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "EncryptionMode" : { + "type" : "string", + "description" : "Encryption mode for encrypting artifacts when uploading to S3. Valid values: SSE_S3 and SSE_KMS." + }, + "KmsKeyArn" : { + "type" : "string", + "description" : "KMS key Arn for encrypting artifacts when uploading to S3. You must specify KMS key Arn for SSE_KMS encryption mode only." + } + } + }, + "ResourceToTag" : { + "type" : "string", + "description" : "Specifies which resources canary tags should be replicated to.", + "enum" : [ "lambda-function" ] + }, + "RetryConfig" : { + "type" : "object", + "additionalProperties" : false, + "properties" : { + "MaxRetries" : { + "type" : "integer", + "description" : "maximum times the canary will be retried upon the scheduled run failure" + } + }, + "required" : [ "MaxRetries" ] + } + }, + "required" : [ "Name", "Code", "ArtifactS3Location", "ExecutionRoleArn", "Schedule", "RuntimeVersion" ], + "tagging" : { + "taggable" : true, + "tagOnCreate" : true, + "tagUpdatable" : true, + "cloudFormationSystemTags" : true, + "tagProperty" : "/properties/Tags", + "permissions" : [ "synthetics:TagResource", "synthetics:UntagResource" ] + }, + "handlers" : { + "create" : { + "permissions" : [ "synthetics:CreateCanary", "synthetics:StartCanary", "synthetics:GetCanary", "synthetics:TagResource", "s3:CreateBucket", "s3:GetObject", "s3:GetObjectVersion", "s3:PutBucketEncryption", "s3:PutEncryptionConfiguration", "s3:GetBucketLocation", "lambda:CreateFunction", "lambda:AddPermission", "lambda:PublishVersion", "lambda:UpdateFunctionCode", "lambda:UpdateFunctionConfiguration", "lambda:GetFunctionConfiguration", "lambda:GetLayerVersionByArn", "lambda:GetLayerVersion", "lambda:PublishLayerVersion", "lambda:TagResource", "ec2:DescribeVpcs", "ec2:DescribeSubnets", "ec2:DescribeSecurityGroups", "iam:PassRole" ] + }, + "update" : { + "permissions" : [ "synthetics:UpdateCanary", "synthetics:StartCanary", "synthetics:StartCanaryDryRun", "synthetics:StopCanary", "synthetics:GetCanary", "synthetics:GetCanaryRuns", "synthetics:TagResource", "synthetics:UntagResource", "s3:GetObject", "s3:GetObjectVersion", "s3:PutBucketEncryption", "s3:PutEncryptionConfiguration", "s3:GetBucketLocation", "lambda:AddPermission", "lambda:PublishVersion", "lambda:UpdateFunctionCode", "lambda:UpdateFunctionConfiguration", "lambda:GetFunctionConfiguration", "lambda:GetLayerVersionByArn", "lambda:GetLayerVersion", "lambda:PublishLayerVersion", "lambda:ListTags", "lambda:TagResource", "lambda:UntagResource", "iam:PassRole", "ec2:DescribeVpcs", "ec2:DescribeSubnets", "ec2:DescribeSecurityGroups" ] + }, + "read" : { + "permissions" : [ "synthetics:GetCanary", "synthetics:DescribeCanaries", "synthetics:ListTagsForResource", "iam:ListRoles", "s3:ListAllMyBuckets", "s3:GetBucketLocation" ] + }, + "delete" : { + "permissions" : [ "synthetics:DeleteCanary", "synthetics:GetCanary", "lambda:DeleteFunction", "lambda:DeleteLayerVersion" ] + }, + "list" : { + "permissions" : [ "synthetics:DescribeCanaries" ] + } + }, + "additionalProperties" : false, + "createOnlyProperties" : [ "/properties/Name" ], + "primaryIdentifier" : [ "/properties/Name" ], + "readOnlyProperties" : [ "/properties/Id", "/properties/State", "/properties/Code/SourceLocationArn" ], + "writeOnlyProperties" : [ "/properties/Code/S3Bucket", "/properties/Code/S3Key", "/properties/Code/S3ObjectVersion", "/properties/Code/Script", "/properties/DeleteLambdaResourcesOnCanaryDeletion", "/properties/StartCanaryAfterCreation", "/properties/ResourcesToReplicateTags", "/properties/RunConfig/EnvironmentVariables", "/properties/VisualReference", "/properties/DryRunAndUpdate" ], + "deprecatedProperties" : [ "/properties/DeleteLambdaResourcesOnCanaryDeletion" ] +} \ No newline at end of file diff --git a/tst/unit/schema/transformers/AddWriteOnlyRequiredPropertiesTransformer.test.ts b/tst/unit/schema/transformers/AddWriteOnlyRequiredPropertiesTransformer.test.ts new file mode 100644 index 00000000..c5f335f5 --- /dev/null +++ b/tst/unit/schema/transformers/AddWriteOnlyRequiredPropertiesTransformer.test.ts @@ -0,0 +1,158 @@ +import { describe, it, expect } from 'vitest'; +import { AddWriteOnlyRequiredPropertiesTransformer } from '../../../../src/schema/transformers/AddWriteOnlyRequiredPropertiesTransformer'; +import { combinedSchemas } from '../../../utils/SchemaUtils'; + +describe('AddWriteOnlyRequiredPropertiesTransformer', () => { + const schemas = combinedSchemas(); + const transformer = new AddWriteOnlyRequiredPropertiesTransformer(); + const PLACEHOLDER = '${1:update required write only property}'; + + const resourceTests = [ + { + typeName: 'AWS::Lambda::Function', + properties: { FunctionName: 'test-function', Role: 'arn:aws:iam::123456789012:role/lambda-role' }, + expectedAfterTransform: { + FunctionName: 'test-function', + Role: 'arn:aws:iam::123456789012:role/lambda-role', + Code: PLACEHOLDER, + }, + }, + { + typeName: 'AWS::EC2::LaunchTemplate', + properties: { LaunchTemplateName: 'test-template' }, + expectedAfterTransform: { + LaunchTemplateName: 'test-template', + LaunchTemplateData: PLACEHOLDER, + }, + }, + { + typeName: 'AWS::Synthetics::Canary', + properties: { + Name: 'test-canary', + ExecutionRoleArn: 'arn:aws:iam::123456789012:role/canary-role', + Schedule: { Expression: 'rate(5 minutes)' }, + RuntimeVersion: 'syn-nodejs-puppeteer-3.9', + ArtifactS3Location: 's3://my-bucket/canary', + }, + expectedAfterTransform: { + Name: 'test-canary', + ExecutionRoleArn: 'arn:aws:iam::123456789012:role/canary-role', + Schedule: { Expression: 'rate(5 minutes)' }, + RuntimeVersion: 'syn-nodejs-puppeteer-3.9', + ArtifactS3Location: 's3://my-bucket/canary', + Code: PLACEHOLDER, + }, + }, + { + typeName: 'AWS::SecurityLake::SubscriberNotification', + properties: { SubscriberArn: 'arn:aws:securitylake:us-east-1:123456789012:subscriber/test' }, + expectedAfterTransform: { + SubscriberArn: 'arn:aws:securitylake:us-east-1:123456789012:subscriber/test', + NotificationConfiguration: PLACEHOLDER, + }, + }, + { + typeName: 'AWS::DynamoDB::GlobalTable', + properties: { + TableName: 'test-table', + KeySchema: [{ AttributeName: 'id', KeyType: 'HASH' }], + AttributeDefinitions: [{ AttributeName: 'id', AttributeType: 'S' }], + Replicas: [{ Region: 'us-east-1' }], + }, + expectedAfterTransform: { + TableName: 'test-table', + KeySchema: [{ AttributeName: 'id', KeyType: 'HASH' }], + AttributeDefinitions: [{ AttributeName: 'id', AttributeType: 'S' }], + Replicas: [{ Region: 'us-east-1' }], + }, + }, + { + typeName: 'AWS::EC2::SpotFleet', + properties: { + SpotFleetRequestConfigData: { + IamFleetRole: 'arn:aws:iam::123456789012:role/fleet-role', + TargetCapacity: 1, + }, + }, + expectedAfterTransform: { + SpotFleetRequestConfigData: { + IamFleetRole: 'arn:aws:iam::123456789012:role/fleet-role', + TargetCapacity: 1, + }, + }, + }, + { + typeName: 'AWS::ElasticLoadBalancingV2::ListenerRule', + properties: { + ListenerArn: 'arn:aws:elasticloadbalancing:us-east-1:123456789012:listener/test', + Priority: 1, + Conditions: [{ Field: 'path-pattern', Values: ['/test'] }], + }, + expectedAfterTransform: { + ListenerArn: 'arn:aws:elasticloadbalancing:us-east-1:123456789012:listener/test', + Priority: 1, + Conditions: [{ Field: 'path-pattern', Values: ['/test'] }], + Actions: PLACEHOLDER, + }, + }, + { + typeName: 'AWS::ElasticLoadBalancingV2::Listener', + properties: { + LoadBalancerArn: 'arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/test', + }, + expectedAfterTransform: { + LoadBalancerArn: 'arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/test', + DefaultActions: PLACEHOLDER, + }, + }, + ]; + + for (const { typeName, properties, expectedAfterTransform } of resourceTests) { + it(`should add required write-only properties to ${typeName}`, () => { + const schema = schemas.schemas.get(typeName); + if (!schema) { + throw new Error(`Schema not found for ${typeName}`); + } + + const resourceProperties = structuredClone(properties); + transformer.transform(resourceProperties, schema); + + expect(resourceProperties).toEqual(expectedAfterTransform); + }); + } + + it('should not modify resources without required write-only properties', () => { + const schema = schemas.schemas.get('AWS::S3::Bucket')!; + const resourceProperties = { BucketName: 'my-bucket' }; + + transformer.transform(resourceProperties, schema); + + expect(resourceProperties).toEqual({ BucketName: 'my-bucket' }); + }); + + it('should not overwrite existing properties', () => { + const schema = schemas.schemas.get('AWS::Lambda::Function')!; + const resourceProperties = { + FunctionName: 'test-function', + Role: 'arn:aws:iam::123456789012:role/lambda-role', + Code: { S3Bucket: 'my-bucket', S3Key: 'my-key' }, + }; + + transformer.transform(resourceProperties, schema); + + expect(resourceProperties.Code).toEqual({ S3Bucket: 'my-bucket', S3Key: 'my-key' }); + }); + + it('should replace empty objects with placeholder', () => { + const schema = schemas.schemas.get('AWS::Lambda::Function')!; + const resourceProperties = { + FunctionName: 'test-function', + Role: 'arn:aws:iam::123456789012:role/lambda-role', + Code: {}, + }; + + transformer.transform(resourceProperties, schema); + + expect(resourceProperties.Code).toEqual(PLACEHOLDER); + }); +}); diff --git a/tst/utils/SchemaUtils.ts b/tst/utils/SchemaUtils.ts index f9ed6596..cab0be93 100644 --- a/tst/utils/SchemaUtils.ts +++ b/tst/utils/SchemaUtils.ts @@ -86,6 +86,51 @@ export const Schemas = { return readFileSync(join(__dirname, '..', 'resources', 'schemas', 'aws-ssm-parameter.json'), 'utf8'); }, }, + DynamoDBGlobalTable: { + fileName: 'file://aws-dynamodb-globaltable.json', + get contents() { + return readFileSync(join(__dirname, '..', 'resources', 'schemas', 'aws-dynamodb-globaltable.json'), 'utf8'); + }, + }, + EC2SpotFleet: { + fileName: 'file://aws-ec2-spotfleet.json', + get contents() { + return readFileSync(join(__dirname, '..', 'resources', 'schemas', 'aws-ec2-spotfleet.json'), 'utf8'); + }, + }, + ELBv2ListenerRule: { + fileName: 'file://aws-elasticloadbalancingv2-listenerrule.json', + get contents() { + return readFileSync( + join(__dirname, '..', 'resources', 'schemas', 'aws-elasticloadbalancingv2-listenerrule.json'), + 'utf8', + ); + }, + }, + ELBv2Listener: { + fileName: 'file://aws-elasticloadbalancingv2-listener.json', + get contents() { + return readFileSync( + join(__dirname, '..', 'resources', 'schemas', 'aws-elasticloadbalancingv2-listener.json'), + 'utf8', + ); + }, + }, + SecurityLakeSubscriberNotification: { + fileName: 'file://aws-securitylake-subscribernotification.json', + get contents() { + return readFileSync( + join(__dirname, '..', 'resources', 'schemas', 'aws-securitylake-subscribernotification.json'), + 'utf8', + ); + }, + }, + SyntheticsCanary: { + fileName: 'file://aws-synthetics-canary.json', + get contents() { + return readFileSync(join(__dirname, '..', 'resources', 'schemas', 'aws-synthetics-canary.json'), 'utf8'); + }, + }, }; export function regionalSchemas(