UNPKG

aws-cdk

Version:

CDK Toolkit, the command line tool for CDK apps

768 lines 95.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); /* eslint-disable import/order */ const cxschema = require("@aws-cdk/cloud-assembly-schema"); const AWS = require("aws-sdk-mock"); const load_balancers_1 = require("../../lib/context-providers/load-balancers"); const mock_sdk_1 = require("../util/mock-sdk"); AWS.setSDK(require.resolve('aws-sdk')); const mockSDK = new mock_sdk_1.MockSdkProvider(); afterEach(done => { AWS.restore(); done(); }); describe('utilities', () => { test('describeTags yields tags by chunk', async () => { const resourceTags = {}; for (const resourceArn of [...Array(100)].map((_, i) => `arn:load-balancer-${i}`)) { resourceTags[resourceArn] = { ResourceArn: resourceArn, Tags: [ { Key: 'name', Value: resourceArn }, ], }; } ; AWS.mock('ELBv2', 'describeTags', (_params, cb) => { expect(_params.ResourceArns.length).toBeLessThanOrEqual(20); cb(null, { TagDescriptions: _params.ResourceArns.map(resourceArn => ({ ResourceArn: resourceArn, Tags: [ { Key: 'name', Value: resourceArn }, ], })), }); }); const elbv2 = (await mockSDK.forEnvironment()).sdk.elbv2(); const resourceTagsOut = {}; for await (const tagDescription of (0, load_balancers_1.describeTags)(elbv2, Object.keys(resourceTags))) { resourceTagsOut[tagDescription.ResourceArn] = tagDescription; } expect(resourceTagsOut).toEqual(resourceTags); }); test('describeListenersByLoadBalancerArn traverses pages', async () => { // arn:listener-0, arn:listener-1, ..., arn:listener-99 const listenerArns = [...Array(100)].map((_, i) => `arn:listener-${i}`); expect(listenerArns[0]).toEqual('arn:listener-0'); AWS.mock('ELBv2', 'describeListeners', (_params, cb) => { const start = parseInt(_params.Marker ?? '0'); const end = start + 10; const slice = listenerArns.slice(start, end); cb(null, { Listeners: slice.map(arn => ({ ListenerArn: arn, })), NextMarker: end < listenerArns.length ? end.toString() : undefined, }); }); const elbv2 = (await mockSDK.forEnvironment()).sdk.elbv2(); const listenerArnsFromPages = Array(); for await (const listener of (0, load_balancers_1.describeListenersByLoadBalancerArn)(elbv2, ['arn:load-balancer'])) { listenerArnsFromPages.push(listener.ListenerArn); } expect(listenerArnsFromPages).toEqual(listenerArns); }); test('describeLoadBalancers traverses pages', async () => { const loadBalancerArns = [...Array(100)].map((_, i) => `arn:load-balancer-${i}`); expect(loadBalancerArns[0]).toEqual('arn:load-balancer-0'); AWS.mock('ELBv2', 'describeLoadBalancers', (_params, cb) => { const start = parseInt(_params.Marker ?? '0'); const end = start + 10; const slice = loadBalancerArns.slice(start, end); cb(null, { LoadBalancers: slice.map(loadBalancerArn => ({ LoadBalancerArn: loadBalancerArn, })), NextMarker: end < loadBalancerArns.length ? end.toString() : undefined, }); }); const elbv2 = (await mockSDK.forEnvironment()).sdk.elbv2(); const loadBalancerArnsFromPages = (await (0, load_balancers_1.describeLoadBalancers)(elbv2, {})).map(l => l.LoadBalancerArn); expect(loadBalancerArnsFromPages).toEqual(loadBalancerArns); }); describe('tagsMatch', () => { test('all tags match', () => { const tagDescription = { ResourceArn: 'arn:whatever', Tags: [{ Key: 'some', Value: 'tag' }], }; const requiredTags = [ { key: 'some', value: 'tag' }, ]; expect((0, load_balancers_1.tagsMatch)(tagDescription, requiredTags)).toEqual(true); }); test('extra tags match', () => { const tagDescription = { ResourceArn: 'arn:whatever', Tags: [ { Key: 'some', Value: 'tag' }, { Key: 'other', Value: 'tag2' }, ], }; const requiredTags = [ { key: 'some', value: 'tag' }, ]; expect((0, load_balancers_1.tagsMatch)(tagDescription, requiredTags)).toEqual(true); }); test('no tags matches no tags', () => { const tagDescription = { ResourceArn: 'arn:whatever', Tags: [], }; expect((0, load_balancers_1.tagsMatch)(tagDescription, [])).toEqual(true); }); test('one tag matches of several', () => { const tagDescription = { ResourceArn: 'arn:whatever', Tags: [{ Key: 'some', Value: 'tag' }], }; const requiredTags = [ { key: 'some', value: 'tag' }, { key: 'other', value: 'value' }, ]; expect((0, load_balancers_1.tagsMatch)(tagDescription, requiredTags)).toEqual(false); }); test('undefined tag does not error', () => { const tagDescription = { ResourceArn: 'arn:whatever', Tags: [{ Key: 'some' }], }; const requiredTags = [ { key: 'some', value: 'tag' }, { key: 'other', value: 'value' }, ]; expect((0, load_balancers_1.tagsMatch)(tagDescription, requiredTags)).toEqual(false); }); }); }); describe('load balancer context provider plugin', () => { test('errors when no matches are found', async () => { // GIVEN const provider = new load_balancers_1.LoadBalancerContextProviderPlugin(mockSDK); mockALBLookup({ loadBalancers: [], }); // WHEN await expect(provider.getValue({ account: '1234', region: 'us-east-1', loadBalancerType: cxschema.LoadBalancerType.APPLICATION, loadBalancerArn: 'arn:load-balancer1', })).rejects.toThrow(/No load balancers found/i); }); test('errors when multiple load balancers match', async () => { // GIVEN const provider = new load_balancers_1.LoadBalancerContextProviderPlugin(mockSDK); mockALBLookup({ loadBalancers: [ { IpAddressType: 'ipv4', LoadBalancerArn: 'arn:load-balancer1', DNSName: 'dns1.example.com', CanonicalHostedZoneId: 'Z1234', SecurityGroups: ['sg-1234'], VpcId: 'vpc-1234', Type: 'application', }, { IpAddressType: 'ipv4', LoadBalancerArn: 'arn:load-balancer2', DNSName: 'dns2.example.com', CanonicalHostedZoneId: 'Z1234', SecurityGroups: ['sg-1234'], VpcId: 'vpc-1234', Type: 'application', }, ], describeTagsExpected: { ResourceArns: ['arn:load-balancer1', 'arn:load-balancer2'] }, tagDescriptions: [ { ResourceArn: 'arn:load-balancer1', Tags: [ { Key: 'some', Value: 'tag' }, ], }, { ResourceArn: 'arn:load-balancer2', Tags: [ { Key: 'some', Value: 'tag' }, ], }, ], }); // WHEN await expect(provider.getValue({ account: '1234', region: 'us-east-1', loadBalancerType: cxschema.LoadBalancerType.APPLICATION, loadBalancerTags: [ { key: 'some', value: 'tag' }, ], })).rejects.toThrow(/Multiple load balancers found/i); }); test('looks up by arn', async () => { // GIVEN const provider = new load_balancers_1.LoadBalancerContextProviderPlugin(mockSDK); mockALBLookup({ describeLoadBalancersExpected: { LoadBalancerArns: ['arn:load-balancer1'] }, loadBalancers: [ { IpAddressType: 'ipv4', LoadBalancerArn: 'arn:load-balancer1', DNSName: 'dns.example.com', CanonicalHostedZoneId: 'Z1234', SecurityGroups: ['sg-1234'], VpcId: 'vpc-1234', Type: 'application', }, ], }); // WHEN const result = await provider.getValue({ account: '1234', region: 'us-east-1', loadBalancerType: cxschema.LoadBalancerType.APPLICATION, loadBalancerArn: 'arn:load-balancer1', }); // THEN expect(result.ipAddressType).toEqual('ipv4'); expect(result.loadBalancerArn).toEqual('arn:load-balancer1'); expect(result.loadBalancerCanonicalHostedZoneId).toEqual('Z1234'); expect(result.loadBalancerDnsName).toEqual('dns.example.com'); expect(result.securityGroupIds).toEqual(['sg-1234']); expect(result.vpcId).toEqual('vpc-1234'); }); test('looks up by tags', async () => { // GIVEN const provider = new load_balancers_1.LoadBalancerContextProviderPlugin(mockSDK); mockALBLookup({ loadBalancers: [ { IpAddressType: 'ipv4', LoadBalancerArn: 'arn:load-balancer1', DNSName: 'dns1.example.com', CanonicalHostedZoneId: 'Z1234', SecurityGroups: ['sg-1234'], VpcId: 'vpc-1234', Type: 'application', }, { IpAddressType: 'ipv4', LoadBalancerArn: 'arn:load-balancer2', DNSName: 'dns2.example.com', CanonicalHostedZoneId: 'Z1234', SecurityGroups: ['sg-1234'], VpcId: 'vpc-1234', Type: 'application', }, ], describeTagsExpected: { ResourceArns: ['arn:load-balancer1', 'arn:load-balancer2'] }, tagDescriptions: [ { ResourceArn: 'arn:load-balancer1', Tags: [ { Key: 'some', Value: 'tag' }, ], }, { ResourceArn: 'arn:load-balancer2', Tags: [ { Key: 'some', Value: 'tag' }, { Key: 'second', Value: 'tag2' }, ], }, ], }); // WHEN const result = await provider.getValue({ account: '1234', region: 'us-east-1', loadBalancerType: cxschema.LoadBalancerType.APPLICATION, loadBalancerTags: [ { key: 'some', value: 'tag' }, { key: 'second', value: 'tag2' }, ], }); expect(result.loadBalancerArn).toEqual('arn:load-balancer2'); }); test('filters by type', async () => { // GIVEN const provider = new load_balancers_1.LoadBalancerContextProviderPlugin(mockSDK); mockALBLookup({ loadBalancers: [ { IpAddressType: 'ipv4', Type: 'network', LoadBalancerArn: 'arn:load-balancer1', DNSName: 'dns1.example.com', CanonicalHostedZoneId: 'Z1234', SecurityGroups: ['sg-1234'], VpcId: 'vpc-1234', }, { IpAddressType: 'ipv4', Type: 'application', LoadBalancerArn: 'arn:load-balancer2', DNSName: 'dns2.example.com', CanonicalHostedZoneId: 'Z1234', SecurityGroups: ['sg-1234'], VpcId: 'vpc-1234', }, ], tagDescriptions: [ { ResourceArn: 'arn:load-balancer1', Tags: [{ Key: 'some', Value: 'tag' }], }, { ResourceArn: 'arn:load-balancer2', Tags: [{ Key: 'some', Value: 'tag' }], }, ], }); // WHEN const loadBalancer = await provider.getValue({ account: '1234', region: 'us-east-1', loadBalancerTags: [{ key: 'some', value: 'tag' }], loadBalancerType: cxschema.LoadBalancerType.APPLICATION, }); expect(loadBalancer.loadBalancerArn).toEqual('arn:load-balancer2'); }); }); describe('load balancer listener context provider plugin', () => { test('errors when no associated load balancers match', async () => { // GIVEN const provider = new load_balancers_1.LoadBalancerListenerContextProviderPlugin(mockSDK); mockALBLookup({ loadBalancers: [], }); // WHEN await expect(provider.getValue({ account: '1234', region: 'us-east-1', loadBalancerType: cxschema.LoadBalancerType.APPLICATION, loadBalancerTags: [{ key: 'some', value: 'tag' }], })).rejects.toThrow(/No associated load balancers found/i); }); test('errors when no listeners match', async () => { // GIVEN const provider = new load_balancers_1.LoadBalancerListenerContextProviderPlugin(mockSDK); mockALBLookup({ loadBalancers: [ { LoadBalancerArn: 'arn:load-balancer', Type: 'application', }, ], listeners: [ { LoadBalancerArn: 'arn:load-balancer', ListenerArn: 'arn:listener', Port: 80, Protocol: 'HTTP', }, ], }); // WHEN await expect(provider.getValue({ account: '1234', region: 'us-east-1', loadBalancerType: cxschema.LoadBalancerType.APPLICATION, loadBalancerArn: 'arn:load-balancer', listenerPort: 443, listenerProtocol: cxschema.LoadBalancerListenerProtocol.HTTPS, })).rejects.toThrow(/No load balancer listeners found/i); }); test('errors when multiple listeners match', async () => { // GIVEN const provider = new load_balancers_1.LoadBalancerListenerContextProviderPlugin(mockSDK); mockALBLookup({ loadBalancers: [ { LoadBalancerArn: 'arn:load-balancer', Type: 'application', }, { LoadBalancerArn: 'arn:load-balancer2', Type: 'application', }, ], tagDescriptions: [ { ResourceArn: 'arn:load-balancer', Tags: [{ Key: 'some', Value: 'tag' }], }, { ResourceArn: 'arn:load-balancer2', Tags: [{ Key: 'some', Value: 'tag' }], }, ], listeners: [ { LoadBalancerArn: 'arn:load-balancer', ListenerArn: 'arn:listener', Port: 80, Protocol: 'HTTP', }, { LoadBalancerArn: 'arn:load-balancer2', ListenerArn: 'arn:listener2', Port: 80, Protocol: 'HTTP', }, ], }); // WHEN await expect(provider.getValue({ account: '1234', region: 'us-east-1', loadBalancerType: cxschema.LoadBalancerType.APPLICATION, loadBalancerTags: [{ key: 'some', value: 'tag' }], listenerPort: 80, listenerProtocol: cxschema.LoadBalancerListenerProtocol.HTTP, })).rejects.toThrow(/Multiple load balancer listeners/i); }); test('looks up by listener arn', async () => { // GIVEN const provider = new load_balancers_1.LoadBalancerListenerContextProviderPlugin(mockSDK); mockALBLookup({ describeListenersExpected: { ListenerArns: ['arn:listener-arn'] }, listeners: [ { ListenerArn: 'arn:listener-arn', LoadBalancerArn: 'arn:load-balancer-arn', Port: 999, }, ], describeLoadBalancersExpected: { LoadBalancerArns: ['arn:load-balancer-arn'] }, loadBalancers: [ { LoadBalancerArn: 'arn:load-balancer-arn', SecurityGroups: ['sg-1234', 'sg-2345'], Type: 'application', }, ], }); // WHEN const listener = await provider.getValue({ account: '1234', region: 'us-east-1', loadBalancerType: cxschema.LoadBalancerType.APPLICATION, listenerArn: 'arn:listener-arn', }); // THEN expect(listener.listenerArn).toEqual('arn:listener-arn'); expect(listener.listenerPort).toEqual(999); expect(listener.securityGroupIds).toEqual(['sg-1234', 'sg-2345']); }); test('looks up by associated load balancer arn', async () => { // GIVEN const provider = new load_balancers_1.LoadBalancerListenerContextProviderPlugin(mockSDK); mockALBLookup({ describeLoadBalancersExpected: { LoadBalancerArns: ['arn:load-balancer-arn1'] }, loadBalancers: [ { LoadBalancerArn: 'arn:load-balancer-arn1', SecurityGroups: ['sg-1234'], Type: 'application', }, ], describeListenersExpected: { LoadBalancerArn: 'arn:load-balancer-arn1' }, listeners: [ { // This one ListenerArn: 'arn:listener-arn1', LoadBalancerArn: 'arn:load-balancer-arn1', Port: 80, }, ], }); // WHEN const listener = await provider.getValue({ account: '1234', region: 'us-east-1', loadBalancerType: cxschema.LoadBalancerType.APPLICATION, loadBalancerArn: 'arn:load-balancer-arn1', }); // THEN expect(listener.listenerArn).toEqual('arn:listener-arn1'); expect(listener.listenerPort).toEqual(80); expect(listener.securityGroupIds).toEqual(['sg-1234']); }); test('looks up by associated load balancer tags', async () => { // GIVEN const provider = new load_balancers_1.LoadBalancerListenerContextProviderPlugin(mockSDK); mockALBLookup({ describeLoadBalancersExpected: { LoadBalancerArns: undefined }, loadBalancers: [ { // This one should have the wrong tags LoadBalancerArn: 'arn:load-balancer-arn1', SecurityGroups: ['sg-1234', 'sg-2345'], Type: 'application', }, { // Expecting this one LoadBalancerArn: 'arn:load-balancer-arn2', SecurityGroups: ['sg-3456', 'sg-4567'], Type: 'application', }, ], describeTagsExpected: { ResourceArns: ['arn:load-balancer-arn1', 'arn:load-balancer-arn2'] }, tagDescriptions: [ { ResourceArn: 'arn:load-balancer-arn1', Tags: [], }, { // Expecting this one ResourceArn: 'arn:load-balancer-arn2', Tags: [ { Key: 'some', Value: 'tag' }, ], }, ], describeListenersExpected: { LoadBalancerArn: 'arn:load-balancer-arn2' }, listeners: [ { // This one ListenerArn: 'arn:listener-arn1', LoadBalancerArn: 'arn:load-balancer-arn2', Port: 80, }, { ListenerArn: 'arn:listener-arn2', LoadBalancerArn: 'arn:load-balancer-arn2', Port: 999, }, ], }); // WHEN const listener = await provider.getValue({ account: '1234', region: 'us-east-1', loadBalancerType: cxschema.LoadBalancerType.APPLICATION, loadBalancerTags: [ { key: 'some', value: 'tag' }, ], listenerPort: 999, }); // THEN expect(listener.listenerArn).toEqual('arn:listener-arn2'); expect(listener.listenerPort).toEqual(999); expect(listener.securityGroupIds).toEqual(['sg-3456', 'sg-4567']); }); test('looks up by listener port and proto', async () => { // GIVEN const provider = new load_balancers_1.LoadBalancerListenerContextProviderPlugin(mockSDK); AWS.mock('ELBv2', 'describeLoadBalancers', (_params, cb) => { expect(_params).toEqual({}); cb(null, { LoadBalancers: [ { // Shouldn't have any matching listeners IpAddressType: 'ipv4', LoadBalancerArn: 'arn:load-balancer1', DNSName: 'dns1.example.com', CanonicalHostedZoneId: 'Z1234', SecurityGroups: ['sg-1234'], VpcId: 'vpc-1234', Type: 'application', }, { // Should have a matching listener IpAddressType: 'ipv4', LoadBalancerArn: 'arn:load-balancer2', DNSName: 'dns2.example.com', CanonicalHostedZoneId: 'Z1234', SecurityGroups: ['sg-2345'], VpcId: 'vpc-1234', Type: 'application', }, ], }); }); AWS.mock('ELBv2', 'describeTags', (_params, cb) => { cb(null, { TagDescriptions: [ { ResourceArn: 'arn:load-balancer1', Tags: [{ Key: 'some', Value: 'tag' }], }, { ResourceArn: 'arn:load-balancer2', Tags: [{ Key: 'some', Value: 'tag' }], }, ], }); }); AWS.mock('ELBv2', 'describeListeners', (params, cb) => { if (params.LoadBalancerArn === 'arn:load-balancer1') { cb(null, { Listeners: [ { // Wrong port, wrong protocol => no match ListenerArn: 'arn:listener-arn1', LoadBalancerArn: 'arn:load-balancer1', Protocol: 'HTTP', Port: 80, }, { // Wrong protocol, right port => no match ListenerArn: 'arn:listener-arn3', LoadBalancerArn: 'arn:load-balancer1', Protocol: 'HTTPS', Port: 443, }, { // Wrong port, right protocol => no match ListenerArn: 'arn:listener-arn4', LoadBalancerArn: 'arn:load-balancer1', Protocol: 'TCP', Port: 999, }, ], }); } else if (params.LoadBalancerArn === 'arn:load-balancer2') { cb(null, { Listeners: [ { // Wrong port, wrong protocol => no match ListenerArn: 'arn:listener-arn5', LoadBalancerArn: 'arn:load-balancer2', Protocol: 'HTTP', Port: 80, }, { // Right port, right protocol => match ListenerArn: 'arn:listener-arn6', LoadBalancerArn: 'arn:load-balancer2', Port: 443, Protocol: 'TCP', }, ], }); } else { cb(new Error(`Unexpected request: ${JSON.stringify(params)}'`), {}); } }); // WHEN const listener = await provider.getValue({ account: '1234', region: 'us-east-1', loadBalancerType: cxschema.LoadBalancerType.APPLICATION, loadBalancerTags: [{ key: 'some', value: 'tag' }], listenerProtocol: cxschema.LoadBalancerListenerProtocol.TCP, listenerPort: 443, }); // THEN expect(listener.listenerArn).toEqual('arn:listener-arn6'); expect(listener.listenerPort).toEqual(443); expect(listener.securityGroupIds).toEqual(['sg-2345']); }); test('filters by associated load balancer type', async () => { // GIVEN const provider = new load_balancers_1.LoadBalancerListenerContextProviderPlugin(mockSDK); mockALBLookup({ describeLoadBalancersExpected: { LoadBalancerArns: undefined }, loadBalancers: [ { // This one has wrong type => no match LoadBalancerArn: 'arn:load-balancer-arn1', SecurityGroups: [], Type: 'application', }, { // Right type => match LoadBalancerArn: 'arn:load-balancer-arn2', SecurityGroups: [], Type: 'network', }, ], tagDescriptions: [ { ResourceArn: 'arn:load-balancer-arn1', Tags: [{ Key: 'some', Value: 'tag' }], }, { ResourceArn: 'arn:load-balancer-arn2', Tags: [{ Key: 'some', Value: 'tag' }], }, ], describeListenersExpected: { LoadBalancerArn: 'arn:load-balancer-arn2' }, listeners: [ { ListenerArn: 'arn:listener-arn2', LoadBalancerArn: 'arn:load-balancer-arn2', Port: 443, }, ], }); // WHEN const listener = await provider.getValue({ account: '1234', region: 'us-east-1', loadBalancerType: cxschema.LoadBalancerType.NETWORK, loadBalancerTags: [{ key: 'some', value: 'tag' }], listenerPort: 443, }); // THEN expect(listener.listenerArn).toEqual('arn:listener-arn2'); expect(listener.listenerPort).toEqual(443); }); test('errors when associated load balancer is wrong type', async () => { // GIVEN const provider = new load_balancers_1.LoadBalancerListenerContextProviderPlugin(mockSDK); mockALBLookup({ describeListenersExpected: { ListenerArns: ['arn:listener-arn1'] }, listeners: [ { ListenerArn: 'arn:listener-arn1', LoadBalancerArn: 'arn:load-balancer-arn1', Port: 443, }, ], describeLoadBalancersExpected: { LoadBalancerArns: ['arn:load-balancer-arn1'] }, loadBalancers: [ { // This one has wrong type => no match LoadBalancerArn: 'arn:load-balancer-arn1', SecurityGroups: [], Type: 'application', }, ], }); // WHEN await expect(provider.getValue({ account: '1234', region: 'us-east-1', loadBalancerType: cxschema.LoadBalancerType.NETWORK, listenerArn: 'arn:listener-arn1', })).rejects.toThrow(/no associated load balancer found/i); }); }); function mockALBLookup(options) { AWS.mock('ELBv2', 'describeLoadBalancers', (_params, cb) => { if (options.describeLoadBalancersExpected !== undefined) { expect(_params).toEqual(options.describeLoadBalancersExpected); } cb(null, { LoadBalancers: options.loadBalancers }); }); AWS.mock('ELBv2', 'describeTags', (_params, cb) => { if (options.describeTagsExpected !== undefined) { expect(_params).toEqual(options.describeTagsExpected); } cb(null, { TagDescriptions: options.tagDescriptions }); }); AWS.mock('ELBv2', 'describeListeners', (_params, cb) => { if (options.describeListenersExpected !== undefined) { expect(_params).toEqual(options.describeListenersExpected); } cb(null, { Listeners: options.listeners }); }); } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibG9hZC1iYWxhbmNlcnMudGVzdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImxvYWQtYmFsYW5jZXJzLnRlc3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBQSxpQ0FBaUM7QUFDakMsMkRBQTJEO0FBRTNELG9DQUFvQztBQUNwQywrRUFBOE47QUFDOU4sK0NBQW1EO0FBRW5ELEdBQUcsQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDO0FBRXZDLE1BQU0sT0FBTyxHQUFHLElBQUksMEJBQWUsRUFBRSxDQUFDO0FBSXRDLFNBQVMsQ0FBQyxJQUFJLENBQUMsRUFBRTtJQUNmLEdBQUcsQ0FBQyxPQUFPLEVBQUUsQ0FBQztJQUNkLElBQUksRUFBRSxDQUFDO0FBQ1QsQ0FBQyxDQUFDLENBQUM7QUFFSCxRQUFRLENBQUMsV0FBVyxFQUFFLEdBQUcsRUFBRTtJQUN6QixJQUFJLENBQUMsbUNBQW1DLEVBQUUsS0FBSyxJQUFJLEVBQUU7UUFDbkQsTUFBTSxZQUFZLEdBQTZDLEVBQUUsQ0FBQztRQUNsRSxLQUFLLE1BQU0sV0FBVyxJQUFJLENBQUMsR0FBRyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxxQkFBcUIsQ0FBQyxFQUFFLENBQUMsRUFBRTtZQUNqRixZQUFZLENBQUMsV0FBVyxDQUFDLEdBQUc7Z0JBQzFCLFdBQVcsRUFBRSxXQUFXO2dCQUN4QixJQUFJLEVBQUU7b0JBQ0osRUFBRSxHQUFHLEVBQUUsTUFBTSxFQUFFLEtBQUssRUFBRSxXQUFXLEVBQUU7aUJBQ3BDO2FBQ0YsQ0FBQztTQUNIO1FBQUEsQ0FBQztRQUVGLEdBQUcsQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFLGNBQWMsRUFBRSxDQUFDLE9BQW9DLEVBQUUsRUFBNkMsRUFBRSxFQUFFO1lBQ3hILE1BQU0sQ0FBQyxPQUFPLENBQUMs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