UNPKG

aws-cdk

Version:

CDK Toolkit, the command line tool for CDK apps

794 lines 94.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const setup = require("./hotswap-test-setup"); const common_1 = require("../../../lib/api/hotswap/common"); const s3_bucket_deployments_1 = require("../../../lib/api/hotswap/s3-bucket-deployments"); let mockLambdaInvoke; let hotswapMockSdkProvider; const payloadWithoutCustomResProps = { RequestType: 'Update', ResponseURL: s3_bucket_deployments_1.REQUIRED_BY_CFN, PhysicalResourceId: s3_bucket_deployments_1.REQUIRED_BY_CFN, StackId: s3_bucket_deployments_1.REQUIRED_BY_CFN, RequestId: s3_bucket_deployments_1.REQUIRED_BY_CFN, LogicalResourceId: s3_bucket_deployments_1.REQUIRED_BY_CFN, }; beforeEach(() => { hotswapMockSdkProvider = setup.setupHotswapTests(); mockLambdaInvoke = jest.fn(); hotswapMockSdkProvider.setInvokeLambdaMock(mockLambdaInvoke); }); describe.each([common_1.HotswapMode.FALL_BACK, common_1.HotswapMode.HOTSWAP_ONLY])('%p mode', (hotswapMode) => { test('calls the lambdaInvoke() API when it receives only an asset difference in an S3 bucket deployment and evaluates CFN expressions in S3 Deployment Properties', async () => { // GIVEN setup.setCurrentCfnStackTemplate({ Resources: { S3Deployment: { Type: 'Custom::CDKBucketDeployment', Properties: { ServiceToken: 'a-lambda-arn', SourceBucketNames: ['src-bucket'], SourceObjectKeys: ['src-key-old'], DestinationBucketName: 'dest-bucket', DestinationBucketKeyPrefix: 'my-key/some-old-prefix', }, }, }, }); const cdkStackArtifact = setup.cdkStackArtifactOf({ template: { Resources: { S3Deployment: { Type: 'Custom::CDKBucketDeployment', Properties: { ServiceToken: 'a-lambda-arn', SourceBucketNames: ['src-bucket'], SourceObjectKeys: { 'Fn::Split': [ '-', 'key1-key2-key3', ], }, DestinationBucketName: 'dest-bucket', DestinationBucketKeyPrefix: 'my-key/some-new-prefix', }, }, }, }, }); // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact); // THEN expect(deployStackResult).not.toBeUndefined(); expect(mockLambdaInvoke).toHaveBeenCalledWith({ FunctionName: 'a-lambda-arn', Payload: JSON.stringify({ ...payloadWithoutCustomResProps, ResourceProperties: { SourceBucketNames: ['src-bucket'], SourceObjectKeys: ['key1', 'key2', 'key3'], DestinationBucketName: 'dest-bucket', DestinationBucketKeyPrefix: 'my-key/some-new-prefix', }, }), }); }); test('does not call the invoke() API when a resource with type that is not Custom::CDKBucketDeployment but has the same properties is changed', async () => { // GIVEN setup.setCurrentCfnStackTemplate({ Resources: { S3Deployment: { Type: 'Custom::NotCDKBucketDeployment', Properties: { SourceObjectKeys: ['src-key-old'], }, }, }, }); const cdkStackArtifact = setup.cdkStackArtifactOf({ template: { Resources: { S3Deployment: { Type: 'Custom::NotCDKBucketDeployment', Properties: { SourceObjectKeys: ['src-key-new'], }, }, }, }, }); if (hotswapMode === common_1.HotswapMode.FALL_BACK) { // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact); // THEN expect(deployStackResult).toBeUndefined(); expect(mockLambdaInvoke).not.toHaveBeenCalled(); } else if (hotswapMode === common_1.HotswapMode.HOTSWAP_ONLY) { // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact); // THEN expect(deployStackResult).not.toBeUndefined(); expect(deployStackResult?.noOp).toEqual(true); expect(mockLambdaInvoke).not.toHaveBeenCalled(); } }); test('does not call the invokeLambda() api if the updated Policy has no Roles in CLASSIC mode but does in HOTSWAP_ONLY mode', async () => { // GIVEN setup.setCurrentCfnStackTemplate({ Parameters: { WebsiteBucketParamOld: { Type: 'String' }, WebsiteBucketParamNew: { Type: 'String' }, }, Resources: { S3Deployment: { Type: 'Custom::CDKBucketDeployment', Properties: { ServiceToken: 'a-lambda-arn', SourceObjectKeys: ['src-key-old'], SourceBucketNames: ['src-bucket'], DestinationBucketName: 'dest-bucket', }, }, Policy: { Type: 'AWS::IAM::Policy', Properties: { PolicyName: 'my-policy', PolicyDocument: { Statement: [ { Action: ['s3:GetObject*'], Effect: 'Allow', Resource: { Ref: 'WebsiteBucketParamOld', }, }, ], }, }, }, }, }); const cdkStackArtifact = setup.cdkStackArtifactOf({ template: { Parameters: { WebsiteBucketParamOld: { Type: 'String' }, WebsiteBucketParamNew: { Type: 'String' }, }, Resources: { S3Deployment: { Type: 'Custom::CDKBucketDeployment', Properties: { ServiceToken: 'a-lambda-arn', SourceObjectKeys: ['src-key-new'], SourceBucketNames: ['src-bucket'], DestinationBucketName: 'dest-bucket', }, }, Policy: { Type: 'AWS::IAM::Policy', Properties: { PolicyName: 'my-policy', PolicyDocument: { Statement: [ { Action: ['s3:GetObject*'], Effect: 'Allow', Resource: { Ref: 'WebsiteBucketParamNew', }, }, ], }, }, }, }, }, }); if (hotswapMode === common_1.HotswapMode.FALL_BACK) { // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact); // THEN expect(deployStackResult).toBeUndefined(); expect(mockLambdaInvoke).not.toHaveBeenCalled(); } else if (hotswapMode === common_1.HotswapMode.HOTSWAP_ONLY) { // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact); // THEN expect(deployStackResult).not.toBeUndefined(); expect(mockLambdaInvoke).toHaveBeenCalledWith({ FunctionName: 'a-lambda-arn', Payload: JSON.stringify({ ...payloadWithoutCustomResProps, ResourceProperties: { SourceObjectKeys: ['src-key-new'], SourceBucketNames: ['src-bucket'], DestinationBucketName: 'dest-bucket', }, }), }); } }); test('throws an error when the serviceToken fails evaluation in the template', async () => { // GIVEN setup.setCurrentCfnStackTemplate({ Resources: { S3Deployment: { Type: 'Custom::CDKBucketDeployment', Properties: { ServiceToken: { Ref: 'BadLamba', }, SourceBucketNames: ['src-bucket'], SourceObjectKeys: ['src-key-old'], DestinationBucketName: 'dest-bucket', }, }, }, }); const cdkStackArtifact = setup.cdkStackArtifactOf({ template: { Resources: { S3Deployment: { Type: 'Custom::CDKBucketDeployment', Properties: { ServiceToken: { Ref: 'BadLamba', }, SourceBucketNames: ['src-bucket'], SourceObjectKeys: ['src-key-new'], DestinationBucketName: 'dest-bucket', }, }, }, }, }); // WHEN await expect(() => hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact)).rejects.toThrow(/Parameter or resource 'BadLamba' could not be found for evaluation/); expect(mockLambdaInvoke).not.toHaveBeenCalled(); }); describe('old-style synthesis', () => { const parameters = { WebsiteBucketParamOld: { Type: 'String' }, WebsiteBucketParamNew: { Type: 'String' }, DifferentBucketParamNew: { Type: 'String' }, }; const serviceRole = { Type: 'AWS::IAM::Role', Properties: { AssumeRolePolicyDocument: { Statement: [ { Action: 'sts:AssumeRole', Effect: 'Allow', Principal: { Service: 'lambda.amazonaws.com', }, }, ], Version: '2012-10-17', }, }, }; const policyOld = { Type: 'AWS::IAM::Policy', Properties: { PolicyName: 'my-policy-old', Roles: [ { Ref: 'ServiceRole' }, ], PolicyDocument: { Statement: [ { Action: ['s3:GetObject*'], Effect: 'Allow', Resource: { Ref: 'WebsiteBucketParamOld', }, }, ], }, }, }; const policyNew = { Type: 'AWS::IAM::Policy', Properties: { PolicyName: 'my-policy-new', Roles: [ { Ref: 'ServiceRole' }, ], PolicyDocument: { Statement: [ { Action: ['s3:GetObject*'], Effect: 'Allow', Resource: { Ref: 'WebsiteBucketParamNew', }, }, ], }, }, }; const policy2Old = { Type: 'AWS::IAM::Policy', Properties: { PolicyName: 'my-policy-old-2', Roles: [ { Ref: 'ServiceRole' }, ], PolicyDocument: { Statement: [ { Action: ['s3:GetObject*'], Effect: 'Allow', Resource: { Ref: 'WebsiteBucketParamOld', }, }, ], }, }, }; const policy2New = { Type: 'AWS::IAM::Policy', Properties: { PolicyName: 'my-policy-new-2', Roles: [ { Ref: 'ServiceRole2' }, ], PolicyDocument: { Statement: [ { Action: ['s3:GetObject*'], Effect: 'Allow', Resource: { Ref: 'DifferentBucketParamOld', }, }, ], }, }, }; const deploymentLambda = { Type: 'AWS::Lambda::Function', Role: { 'Fn::GetAtt': [ 'ServiceRole', 'Arn', ], }, }; const s3DeploymentOld = { Type: 'Custom::CDKBucketDeployment', Properties: { ServiceToken: { 'Fn::GetAtt': [ 'S3DeploymentLambda', 'Arn', ], }, SourceBucketNames: ['src-bucket-old'], SourceObjectKeys: ['src-key-old'], DestinationBucketName: 'WebsiteBucketOld', }, }; const s3DeploymentNew = { Type: 'Custom::CDKBucketDeployment', Properties: { ServiceToken: { 'Fn::GetAtt': [ 'S3DeploymentLambda', 'Arn', ], }, SourceBucketNames: ['src-bucket-new'], SourceObjectKeys: ['src-key-new'], DestinationBucketName: 'WebsiteBucketNew', }, }; beforeEach(() => { setup.pushStackResourceSummaries(setup.stackSummaryOf('S3DeploymentLambda', 'AWS::Lambda::Function', 'my-deployment-lambda'), setup.stackSummaryOf('ServiceRole', 'AWS::IAM::Role', 'my-service-role')); }); test('calls the lambdaInvoke() API when it receives an asset difference in an S3 bucket deployment and an IAM Policy difference using old-style synthesis', async () => { // GIVEN setup.setCurrentCfnStackTemplate({ Resources: { Parameters: parameters, ServiceRole: serviceRole, Policy: policyOld, S3DeploymentLambda: deploymentLambda, S3Deployment: s3DeploymentOld, }, }); const cdkStackArtifact = setup.cdkStackArtifactOf({ template: { Resources: { Parameters: parameters, ServiceRole: serviceRole, Policy: policyNew, S3DeploymentLambda: deploymentLambda, S3Deployment: s3DeploymentNew, }, }, }); // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact, { WebsiteBucketParamOld: 'WebsiteBucketOld', WebsiteBucketParamNew: 'WebsiteBucketNew' }); // THEN expect(deployStackResult).not.toBeUndefined(); expect(mockLambdaInvoke).toHaveBeenCalledWith({ FunctionName: 'arn:aws:lambda:here:123456789012:function:my-deployment-lambda', Payload: JSON.stringify({ ...payloadWithoutCustomResProps, ResourceProperties: { SourceBucketNames: ['src-bucket-new'], SourceObjectKeys: ['src-key-new'], DestinationBucketName: 'WebsiteBucketNew', }, }), }); }); test(`does not call the lambdaInvoke() API when the difference in the S3 deployment is referred to in one IAM policy change but not another in CLASSIC mode but does in HOTSWAP_ONLY`, async () => { // GIVEN setup.setCurrentCfnStackTemplate({ Resources: { ServiceRole: serviceRole, Policy1: policyOld, Policy2: policy2Old, S3DeploymentLambda: deploymentLambda, S3Deployment: s3DeploymentOld, }, }); const cdkStackArtifact = setup.cdkStackArtifactOf({ template: { Resources: { ServiceRole: serviceRole, Policy1: policyNew, Policy2: { Properties: { Roles: [ { Ref: 'ServiceRole' }, 'different-role', ], PolicyDocument: { Statement: [ { Action: ['s3:GetObject*'], Effect: 'Allow', Resource: { 'Fn::GetAtt': [ 'DifferentBucketNew', 'Arn', ], }, }, ], }, }, }, S3DeploymentLambda: deploymentLambda, S3Deployment: s3DeploymentNew, }, }, }); if (hotswapMode === common_1.HotswapMode.FALL_BACK) { // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact); // THEN expect(deployStackResult).toBeUndefined(); expect(mockLambdaInvoke).not.toHaveBeenCalled(); } else if (hotswapMode === common_1.HotswapMode.HOTSWAP_ONLY) { // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact); // THEN expect(deployStackResult).not.toBeUndefined(); expect(mockLambdaInvoke).toHaveBeenCalledWith({ FunctionName: 'arn:aws:lambda:here:123456789012:function:my-deployment-lambda', Payload: JSON.stringify({ ...payloadWithoutCustomResProps, ResourceProperties: { SourceBucketNames: ['src-bucket-new'], SourceObjectKeys: ['src-key-new'], DestinationBucketName: 'WebsiteBucketNew', }, }), }); } }); test(`does not call the lambdaInvoke() API when the lambda that references the role is referred to by something other than an S3 deployment in CLASSIC mode but does in HOTSWAP_ONLY mode`, async () => { // GIVEN setup.setCurrentCfnStackTemplate({ Resources: { ServiceRole: serviceRole, Policy: policyOld, S3DeploymentLambda: deploymentLambda, S3Deployment: s3DeploymentOld, Endpoint: { Type: 'AWS::Lambda::Permission', Properties: { Action: 'lambda:InvokeFunction', FunctionName: { 'Fn::GetAtt': [ 'S3DeploymentLambda', 'Arn', ], }, Principal: 'apigateway.amazonaws.com', }, }, }, }); const cdkStackArtifact = setup.cdkStackArtifactOf({ template: { Resources: { ServiceRole: serviceRole, Policy: policyNew, S3DeploymentLambda: deploymentLambda, S3Deployment: s3DeploymentNew, Endpoint: { Type: 'AWS::Lambda::Permission', Properties: { Action: 'lambda:InvokeFunction', FunctionName: { 'Fn::GetAtt': [ 'S3DeploymentLambda', 'Arn', ], }, Principal: 'apigateway.amazonaws.com', }, }, }, }, }); if (hotswapMode === common_1.HotswapMode.FALL_BACK) { // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact); // THEN expect(deployStackResult).toBeUndefined(); expect(mockLambdaInvoke).not.toHaveBeenCalled(); } else if (hotswapMode === common_1.HotswapMode.HOTSWAP_ONLY) { // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact); // THEN expect(deployStackResult).not.toBeUndefined(); expect(mockLambdaInvoke).toHaveBeenCalledWith({ FunctionName: 'arn:aws:lambda:here:123456789012:function:my-deployment-lambda', Payload: JSON.stringify({ ...payloadWithoutCustomResProps, ResourceProperties: { SourceBucketNames: ['src-bucket-new'], SourceObjectKeys: ['src-key-new'], DestinationBucketName: 'WebsiteBucketNew', }, }), }); } }); test('calls the lambdaInvoke() API when it receives an asset difference in two S3 bucket deployments and IAM Policy differences using old-style synthesis', async () => { // GIVEN const s3Deployment2Old = { Type: 'Custom::CDKBucketDeployment', Properties: { ServiceToken: { 'Fn::GetAtt': [ 'S3DeploymentLambda2', 'Arn', ], }, SourceBucketNames: ['src-bucket-old'], SourceObjectKeys: ['src-key-old'], DestinationBucketName: 'DifferentBucketOld', }, }; const s3Deployment2New = { Type: 'Custom::CDKBucketDeployment', Properties: { ServiceToken: { 'Fn::GetAtt': [ 'S3DeploymentLambda2', 'Arn', ], }, SourceBucketNames: ['src-bucket-new'], SourceObjectKeys: ['src-key-new'], DestinationBucketName: 'DifferentBucketNew', }, }; setup.setCurrentCfnStackTemplate({ Resources: { ServiceRole: serviceRole, ServiceRole2: serviceRole, Policy1: policyOld, Policy2: policy2Old, S3DeploymentLambda: deploymentLambda, S3DeploymentLambda2: deploymentLambda, S3Deployment: s3DeploymentOld, S3Deployment2: s3Deployment2Old, }, }); const cdkStackArtifact = setup.cdkStackArtifactOf({ template: { Resources: { Parameters: parameters, ServiceRole: serviceRole, ServiceRole2: serviceRole, Policy1: policyNew, Policy2: policy2New, S3DeploymentLambda: deploymentLambda, S3DeploymentLambda2: deploymentLambda, S3Deployment: s3DeploymentNew, S3Deployment2: s3Deployment2New, }, }, }); // WHEN setup.pushStackResourceSummaries(setup.stackSummaryOf('S3DeploymentLambda2', 'AWS::Lambda::Function', 'my-deployment-lambda-2'), setup.stackSummaryOf('ServiceRole2', 'AWS::IAM::Role', 'my-service-role-2')); const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact, { WebsiteBucketParamOld: 'WebsiteBucketOld', WebsiteBucketParamNew: 'WebsiteBucketNew', DifferentBucketParamNew: 'WebsiteBucketNew', }); // THEN expect(deployStackResult).not.toBeUndefined(); expect(mockLambdaInvoke).toHaveBeenCalledWith({ FunctionName: 'arn:aws:lambda:here:123456789012:function:my-deployment-lambda', Payload: JSON.stringify({ ...payloadWithoutCustomResProps, ResourceProperties: { SourceBucketNames: ['src-bucket-new'], SourceObjectKeys: ['src-key-new'], DestinationBucketName: 'WebsiteBucketNew', }, }), }); expect(mockLambdaInvoke).toHaveBeenCalledWith({ FunctionName: 'arn:aws:lambda:here:123456789012:function:my-deployment-lambda-2', Payload: JSON.stringify({ ...payloadWithoutCustomResProps, ResourceProperties: { SourceBucketNames: ['src-bucket-new'], SourceObjectKeys: ['src-key-new'], DestinationBucketName: 'DifferentBucketNew', }, }), }); }); test(`does not call the lambdaInvoke() API when it receives an asset difference in an S3 bucket deployment that references two different policies in CLASSIC mode but does in HOTSWAP_ONLY mode`, async () => { // GIVEN setup.setCurrentCfnStackTemplate({ Resources: { ServiceRole: serviceRole, Policy1: policyOld, Policy2: policy2Old, S3DeploymentLambda: deploymentLambda, S3Deployment: s3DeploymentOld, }, }); const cdkStackArtifact = setup.cdkStackArtifactOf({ template: { Resources: { ServiceRole: serviceRole, Policy1: policyNew, Policy2: { Properties: { Roles: [ { Ref: 'ServiceRole' }, ], PolicyDocument: { Statement: [ { Action: ['s3:GetObject*'], Effect: 'Allow', Resource: { 'Fn::GetAtt': [ 'DifferentBucketNew', 'Arn', ], }, }, ], }, }, }, S3DeploymentLambda: deploymentLambda, S3Deployment: s3DeploymentNew, }, }, }); if (hotswapMode === common_1.HotswapMode.FALL_BACK) { // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact); // THEN expect(deployStackResult).toBeUndefined(); expect(mockLambdaInvoke).not.toHaveBeenCalled(); } else if (hotswapMode === common_1.HotswapMode.HOTSWAP_ONLY) { // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact); // THEN expect(deployStackResult).not.toBeUndefined(); expect(mockLambdaInvoke).toHaveBeenCalledWith({ FunctionName: 'arn:aws:lambda:here:123456789012:function:my-deployment-lambda', Payload: JSON.stringify({ ...payloadWithoutCustomResProps, ResourceProperties: { SourceBucketNames: ['src-bucket-new'], SourceObjectKeys: ['src-key-new'], DestinationBucketName: 'WebsiteBucketNew', }, }), }); } }); test(`does not call the lambdaInvoke() API when a policy is referenced by a resource that is not an S3 deployment in CLASSIC mode but does in HOTSWAP_ONLY mode`, async () => { // GIVEN setup.setCurrentCfnStackTemplate({ Resources: { ServiceRole: serviceRole, Policy1: policyOld, S3DeploymentLambda: deploymentLambda, S3Deployment: s3DeploymentOld, NotADeployment: { Type: 'AWS::Not::S3Deployment', Properties: { Prop: { Ref: 'ServiceRole', }, }, }, }, }); const cdkStackArtifact = setup.cdkStackArtifactOf({ template: { Resources: { ServiceRole: serviceRole, Policy1: policyNew, S3DeploymentLambda: deploymentLambda, S3Deployment: s3DeploymentNew, NotADeployment: { Type: 'AWS::Not::S3Deployment', Properties: { Prop: { Ref: 'ServiceRole', }, }, }, }, }, }); if (hotswapMode === common_1.HotswapMode.FALL_BACK) { // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact); // THEN expect(deployStackResult).toBeUndefined(); expect(mockLambdaInvoke).not.toHaveBeenCalled(); } else if (hotswapMode === common_1.HotswapMode.HOTSWAP_ONLY) { // WHEN const deployStackResult = await hotswapMockSdkProvider.tryHotswapDeployment(hotswapMode, cdkStackArtifact); // THEN expect(deployStackResult).not.toBeUndefined(); expect(mockLambdaInvoke).toHaveBeenCalledWith({ FunctionName: 'arn:aws:lambda:here:123456789012:function:my-deployment-lambda', Payload: JSON.stringify({ ...payloadWithoutCustomResProps, ResourceProperties: { SourceBucketNames: ['src-bucket-new'], SourceObjectKeys: ['src-key-new'], DestinationBucketName: 'WebsiteBucketNew', }, }), }); } }); }); }); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiczMtYnVja2V0LWhvdHN3YXAtZGVwbG95bWVudHMudGVzdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInMzLWJ1Y2tldC1ob3Rzd2FwLWRlcGxveW1lbnRzLnRlc3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFFQSw4Q0FBOEM7QUFDOUMsNERBQThEO0FBQzlELDBGQUFpRjtBQUVqRixJQUFJLGdCQUE2RixDQUFDO0FBQ2xHLElBQUksc0JBQW9ELENBQUM7QUFFekQsTUFBTSw0QkFBNEIsR0FBRztJQUNuQyxXQUFXLEVBQUUsUUFBUTtJQUNyQixXQUFXLEVBQUUsdUNBQWU7SUFDNUIsa0JBQWtCLEVBQUUsdUNBQWU7SUFDbkMsT0FBTyxFQUFFLHVDQUFlO0lBQ3hCLFNBQVMsRUFBRSx1Q0FBZTtJQUMxQixpQkFBaUIsRUFBRSx1Q0FBZTtDQUNuQyxDQUFDO0FBRUYsVUFBVSxDQUFDLEdBQUcsRUFBRTtJQUNkLHNCQUFzQixHQUFHLEtBQUssQ0FBQyxpQkFBaUIsRUFBRSxDQUFDO0lBQ25ELGdCQUFnQixHQUFHLElBQUksQ0FBQyxFQUFFLEVBQUUsQ0FBQztJQUM3QixzQkFBc0IsQ0FBQyxtQkFBbUIsQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDO0FBQy9ELENBQUMsQ0FBQyxDQUFDO0FBRUgsUUFBUSxDQUFDLElBQUksQ0FBQyxDQUFDLG9CQUFXLENBQUMsU0FBUyxFQUFFLG9CQUFXLENBQUMsWUFBWSxDQUFDLENBQUMsQ0FBQyxTQUFTLEVBQUUsQ0FBQyxXQUFXLEVBQUUsRUFBRTtJQUMxRixJQUFJLENBQUMsNkpBQTZKLEVBQUUsS0FBSyxJQUFJLEVBQUU7UUFDN0ssUUFBUTtRQUNSLEtBQUssQ0FBQywwQkFBMEIsQ0FBQztZQUMvQixTQUFTLEVBQUU7Z0JBQ1QsWUFBWSxFQUFFO29CQUNaLElBQUksRUFBRSw2QkFBNkI7b0JBQ25DLFVBQVUsRUFBRTt3QkFDVixZQUFZLEVBQUUsY0FBYzt3QkFDNUIsaUJBQWlCLEVBQUUsQ0FBQyxZQUFZ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