From bd63b25f8436a9e550ced0a654d5bcd06a269b0a Mon Sep 17 00:00:00 2001 From: mickychetta Date: Tue, 26 Apr 2022 23:05:12 +0000 Subject: [PATCH 1/3] created README.md --- .../aws-fargate-secretsmanager/README.md | 120 ++++++++++++++++++ .../architecture.png | Bin 0 -> 127509 bytes 2 files changed, 120 insertions(+) create mode 100644 source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/README.md create mode 100644 source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/architecture.png diff --git a/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/README.md b/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/README.md new file mode 100644 index 000000000..2e295be39 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/README.md @@ -0,0 +1,120 @@ +# aws-fargate-secretsmanager module + + +--- + +![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge) + +> All classes are under active development and subject to non-backward compatible changes or removal in any +> future version. These are not subject to the [Semantic Versioning](https://semver.org/) model. +> This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. + +--- + + +| **Reference Documentation**:| https://docs.aws.amazon.com/solutions/latest/constructs/| +|:-------------|:-------------| +
+ +| **Language** | **Package** | +|:-------------|-----------------| +|![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png) Python|`aws_solutions_constructs.aws_fargate_secretsmanager`| +|![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png) Typescript|`@aws-solutions-constructs/aws-fargate-secretsmanager`| +|![Java Logo](https://docs.aws.amazon.com/cdk/api/latest/img/java32.png) Java|`software.amazon.awsconstructs.services.fargatesecretsmanager`| + +This AWS Solutions Construct implements an AWS Fargate service that can write/read to an AWS Secrets Manager + +Here is a minimal deployable pattern definition: + +Typescript +``` typescript +import { Construct } from 'constructs'; +import { Stack, StackProps } from 'aws-cdk-lib'; +import { FargateToSecretsmanager, FargateToSecretsmanagerProps } from '@aws-solutions-constructs/aws-fargate-secretsmanager'; + +const constructProps: FargateToSecretsmanagerProps = { + publicApi: true, + ecrRepositoryArn: "arn:aws:ecr:us-east-1:123456789012:repository/your-ecr-repo", +}; + +new FargateToSecretsmanager(stack, 'test-construct', constructProps); +``` + +Python +``` python +from aws_solutions_constructs.aws_fargate_secretsmanager import FargateToSecretsmanager, FargateToSecretsmanagerProps +from aws_cdk import ( + Stack +) +from constructs import Construct + +FargateToSecretsmanager(self, 'test_construct', + public_api=True, + ecr_repository_arn="arn:aws:ecr:us-east-1:123456789012:repository/your-ecr-repo") +``` + +Java +``` java +import software.constructs.Construct; + +import software.amazon.awscdk.Stack; +import software.amazon.awscdk.StackProps; +import software.amazon.awsconstructs.services.fargatesecretsmanager.*; + +new FargateToSecretsmanager(this, "test-construct", new FargateToSecretsmanagerProps.Builder() + .publicApi(true) + .ecrRepositoryArn("arn:aws:ecr:us-east-1:123456789012:repository/your-ecr-repo") + .build()); +``` + +## Pattern Construct Props + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +| publicApi | `boolean` | Whether the construct is deploying a private or public API. This has implications for the VPC. | +| vpcProps? | [`ec2.VpcProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-ec2.VpcProps.html) | Optional custom properties for a VPC the construct will create. This VPC will be used by any Private Hosted Zone the construct creates (that's why loadBalancerProps and privateHostedZoneProps can't include a VPC). Providing both this and existingVpc is an error. | +| existingVpc? | [`ec2.IVpc`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-ec2.IVpc.html) | An existing VPC in which to deploy the construct. Providing both this and vpcProps is an error. If the client provides an existing load balancer and/or existing Private Hosted Zone, those constructs must exist in this VPC. | +| clusterProps? | [`ecs.ClusterProps`](https://docs.aws.amazon.com/cdk/api/v1/docs/@aws-cdk_aws-ecs.ClusterProps.html) | Optional properties to create a new ECS cluster. To provide an existing cluster, use the cluster attribute of fargateServiceProps. | +| ecrRepositoryArn? | `string` | The arn of an ECR Repository containing the image to use to generate the containers. Either this or the image property of containerDefinitionProps must be provided. format: arn:aws:ecr:*region*:*account number*:repository/*Repository Name* | +| ecrImageVersion? | `string` | The version of the image to use from the repository. Defaults to 'Latest' | +| containerDefinitionProps? | [`ecs.ContainerDefinitionProps \| any`](https://docs.aws.amazon.com/cdk/api/v1/docs/@aws-cdk_aws-ecs.ContainerDefinitionProps.html) | Optional props to define the container created for the Fargate Service (defaults found in fargate-defaults.ts) | +| fargateTaskDefinitionProps? | [`ecs.FargateTaskDefinitionProps \| any`](https://docs.aws.amazon.com/cdk/api/v1/docs/@aws-cdk_aws-ecs.FargateTaskDefinitionProps.html) | Optional props to define the Fargate Task Definition for this construct (defaults found in fargate-defaults.ts) | +| fargateServiceProps? | [`ecs.FargateServiceProps \| any`](https://docs.aws.amazon.com/cdk/api/v1/docs/@aws-cdk_aws-ecs.FargateServiceProps.html) | Optional values to override default Fargate Task definition properties (fargate-defaults.ts). The construct will default to launching the service is the most isolated subnets available (precedence: Isolated, Private and Public). Override those and other defaults here. | +| existingFargateServiceObject? | [`ecs.FargateService`](https://docs.aws.amazon.com/cdk/api/v1/docs/@aws-cdk_aws-ecs.FargateService.html) | A Fargate Service already instantiated (probably by another Solutions Construct). If this is specified, then no props defining a new service can be provided, including: ecrImageVersion, containerDefinitionProps, fargateTaskDefinitionProps, ecrRepositoryArn, fargateServiceProps, clusterProps | +| existingContainerDefinitionObject? | [`ecs.ContainerDefinition`](https://docs.aws.amazon.com/cdk/api/v1/docs/@aws-cdk_aws-ecs.ContainerDefinition.html) | A container definition already instantiated as part of a Fargate service. This must be the container in the existingFargateServiceObject | +|secretProps?|[`secretsmanager.SecretProps`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-secretsmanager.SecretProps.html)|Optional user provided props to override the default props for Secrets Manager| +|existingSecretObj?|[`secretsmanager.Secret`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-secretsmanager.Secret.html)|Existing instance of Secrets Manager Secret object, If this is set then the secretProps is ignored| +|grantWriteAccess?|`boolean`|Optional write access to the Secret for the Fargate service (Read-Only by default) +|secretEnvironmentVariableName?|`string`|Optional Name for the Secrets Manager secret environment variable set for the Fargate service.| + +## Pattern Properties + +| **Name** | **Type** | **Description** | +|:-------------|:----------------|-----------------| +| vpc | [`ec2.IVpc`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-ec2.IVpc.html) | The VPC used by the construct (whether created by the construct or provided by the client) | +| service | [`ecs.FargateService`](https://docs.aws.amazon.com/cdk/api/v1/docs/@aws-cdk_aws-ecs.FargateService.html) | The AWS Fargate service used by this construct (whether created by this construct or passed to this construct at initialization) | +| container | [`ecs.ContainerDefinition`](https://docs.aws.amazon.com/cdk/api/v1/docs/@aws-cdk_aws-ecs.ContainerDefinition.html) | The container associated with the AWS Fargate service in the service property. | +|secret|[`secretsmanager.Secret`](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-secretsmanager.Secret.html)|Returns an instance of `secretsmanager.Secret` created by the construct| + +## Default settings + +Out of the box implementation of the Construct without any override will set the following defaults: + +### AWS Fargate Service +* Sets up an AWS Fargate service + * Uses the existing service if provided + * Creates a new service if none provided. + * Service will run in isolated subnets if available, then private subnets if available and finally public subnets + * Adds environment variables to the container with the ARN and Name of the Secrets Manager secret + * Add permissions to the container IAM role allowing it to publish to the Secrets Manager secret + +### Amazon Secrets Manager Secret +* Sets up an Amazon Secrets Manager secret + * Uses an existing secret if one is provided, otherwise creates a new one +* Adds an Interface Endpoint to the VPC for Secrets Manager (the service by default runs in Isolated or Private subnets) + +## Architecture +![Architecture Diagram](architecture.png) + +*** +© Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/architecture.png b/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..e6451b8c605c6f485e66030b1be0804611f84f86 GIT binary patch literal 127509 zcma&N1yodR*EdcKQqrJO0wSq&_rTEIB_Ta9#Lyi>cZYPTAkrWL(ntuB(v76j-S9s= z_xr8&Jumlvt#7RZbLN~g=bUTref{=t#}%QfEQ5nZhJ}QLgd-;_sg8v7Xc!3zB#iM0 zID$PjGY@(;N%0&XqZ@R{Zc&>z#t_?e<8Pwg*L6O6e z!_GlUn%~r((?yrdRmD_SQ`UjklS|WFLfhHJ)ZR+c$yJNj6A+n%w1*d$jyktHpOy`~ zDW?QX*40r;T|-S>MN-)eqNECwJRiFmM2#EJwuH8(i-W7UiKU*St~1<&-Ah&7!cGce=4NLmZsDS*>j(&4 z+R4e3j|Ux$w({C85&|$uS5Fmb0S%ZvmlE8< z+|uw6rWC9M-OK_8b;4MQufSVDQemR?gO1){5HP z$`CCzUUnUGJ4+`U87B?}Wqt`Sc8G$T1zbbY#7auv)16CMiNi%sK)^~zN>S0)Ue3uz z!bTDhc1z zlFEu)>@a>lxSRqzkC%nHwHjcu(x&{*X1pA>Fb4~LZJ3*fq@|~#B9A&dM4O*SR!!cC zAI@%OrsyHTWn&`XF7GBFDX*&TE(GlOY!T1dIddFiOC2zXiO zy109|xO$nnsXFqS>pKGv;dSNXl2w+`RfL2ChD5H zCOoE`Zgy}1;32@Kt*PheZmO-Iq{$^I08{6Y_TqzDs`2oc@p_nPduce>sB6jE$nkTT zNZ!Wi`|^;|l_gcfS2f zx?@OBpP#mHXGs5H&M<8(ntS!r;%I%~$S-$8iCx8S=v8m^>mYK>c%=V_EkGXp79}PD zjPmmTVN(JrD&(Qig|jI?zLGU**srh z-*pg!g9WkP`YS}TF}k&?mQ)}fb*1h3QJYW#R_)*g##*sl07&+~w?xb_DrX81MtqWJ z{F6c+6cH6(LV{6uw`IR?CLh=FJKg~|9;(Ai&A z1|J`I@4pCWI15~e!gA0B|IL18W{jUC6OFWjIC$HB0Y=l`@cEsd3f3To&fULDTP(;& zGjDpNjDqNaQUa7wB2i-gqpzt1Boe6GvCUr&;J4Uwf9wmj-xwrJSh5H`*CFvs zvd*v}!}=4Cd01V{b}I0H4^+187Bof}8;GQry3$(Jq43wQsg^b_32!Cnh}Mm&a^i+& zZ~p6!KhXw;J2f!oGBt#B1S?(RljKd_L5gm+m9Hu1A5dacbO@=D|M!OUw}-+iu4}LX zDsnURH*C9#By#47;C_Afg(_k~AlHeWet6EY<0qIMwf(XFubt4NcHEP81>-%w=sw*IF=-hev?WQdLUJmgX9+v+vnfP0-H{hLlRh>FPz zli`~3y!1C4{xyWL5s7xf3+EZ}K8f-^oTB{xPG`3XcMC#IYwi*#>vY4(Jj3<(h8lo_ zsfw_K6gkO0)Mz7c>LWR+3NZRg3}oiv*S{Hq2Dn)e6YPuQ@|c>)oXJv>vxM{q{1@z^ z{9;abw@aDCyyp`?cmJ2Y{YT|qpugP|Fp)xQj(e3usa5wE{JCpQiM|oA1NNJ{7>UA? zKnjgwj_ct>U;QPB+>O3ME3+QvKsX7mne9wip?RQ!VHGgTG9dfIKJ?MPutU4D?Y0%b zD3-2!cdk$s`2YITPc1+GMF@>VDZ3z+U4m^*w5@QUtp2(zOFC#j@6EnQ=mBL==7dq8 zJ}^RXD5x0Rk58?_fK1q{hoYIcRtl@Od1sATt4#vcqa+goj#DE$V*sMVwK58MV0YAC zF&Qyy-zAp(34-q?p|2p0XlUXq4{!38n%$kQ9w}~BOSIL3^gEkUv1;q2KG{X^T%6VE zblSZ6Bs~~S-mO%(A+unf3jf(h6q$XG82`QLMuK-nt z4e<=WJ^^#~>K+k7%gPXz!_-qa_dj-diJS`Gmc4A^{ol8W2bbd)3tj43^1hp97prNH zezB2_c@#@W;k+hiR!#{R%6h{9C2589y}{BGN_0=7ouv%B%u)MJ|1{bE}NOw=$029_P=i2~TFUwvU3rU*r z!nL2g=s?O+G+wvN;ZYs!x{T-~m}~db_ne3J_*wz1miOsR!FwaqmveD+gS|lq?H4A~ zSCHkaJ^j-K#m-+0_kbYIvfQik`aA+njKeykJPXe)9q;w>yIV-O)kCdJg@IoqCa!-? ztOCMkiS1gEs7VZ4?KzjrVaTFt;1bjA7!e&}7NG^;TH8M?Gm12$@|}r#gyA8*1M7^< z=!_~>;zh#87CzO=al}T&z&-jiH-vy55^f}QF$f>WglF$b0qz@E!~?Imom)vXVs?E|xCso|q;8SLiv`nA}>3?wETkx0R*7%ZSI3>7w~JFDHt^Qy3S4 z?Uo^m6#F5rKKGpZo$xa}z_m0{5vw6S?jhbLO{~)ogOKBsh`Ij~(K4Y9-Ot!?f2M@< zw}D562MP%kEn=(25VQ=XoCu0X4@3cebdTr%_>fQ#Akk3earT$aL{PHFrbPD;T}cMu zROvd$%tM2U#{u51$NWn$nF4Y(ab5!5#WR&?29X0vG=1J2vWCT?2+Z2=WY7k@kSG6f z($9fu|0lnz?jbI^(G6>Ms13oaKXSb5e%l29&@|VI2SC9jgU;0oC!k)w$rwqry42Q# z-@U@mE8udXRr09=%%^laNq85l`aZ;P900~a{|e&-#RBqbd5*OA_;N+Sc)90&N=_`3 zoEQLL&!tc?Sm6&2@BKJm-sABfUb2Ut zE+6Le=(wy%yi_>%Il?4i7a%Z9Slp40Ox**%9IR4DSw)u{*6qo8QuY_zNe@sh08}kQ zoex%MGL$ojxT^ZfvPwYPc0VZ{MO6{0%~mq3XSVl~vb<>g?2v`%Z;bo!6RP6cYwv!L zaNO6N+?R_}=oI&}vq01KutM!92&>k>@8<~b|Msoa$Tb+>iJ=E)>=9ACEN28%`?%r(fSQp`&~dHJJk>s=~9J7W6ie(J$MQtmPXZgb)+n<0QOmu>Q33{2c3vUR}iDbz9({6jb{NbSqQ~r zlx23$zsu>7d~tkHMW*G52@bqe+?J9EOm1Mmy=9j;&3)VH@fvaMp$Q{Sb5zcrr_#x@lm#HRjfE&ZP8 z2jBKWF@Ow)>DDnUsZPvBIgH@H_FhW6&9a{zGuve@ZQGU3H3V;TPw~L zru!&ucHL!E>gY$x+Zrk6Bc|ifT>B~t1n?IoSl&+Mm)Bl%)yQ#tY>|qh0~q0~^`SF)-R-}?e5gH-b?xUpgHBeU~-J9E|33^&`4e#d^OQx*-pd`RkBs&hzqg&_?CW zY&Q1j`ap53sqp*0zHYY$eE(zrq}kGKU_#XXEbLW0ffO$ z4k|=WRh9SD;OZ8wJUG_?D zcc&`|sfSgjo#)(u|MEF*ex-Xim-fsp*Nc8*N%J-G__~|N;7B>IKX^E|s+^5ktq~si zC@d>~dT}4RNLw&Jg<24EEMc!JEv&&1sY z=fTjk-%&Fz3j5O=%l_99Syf-lY9HO@&cGxI{^C%=t|-N!I&B53vz zlNEqbyKW9yaOaV_$D|TM1^UnTvV1`9G!!=~!LZ&C__hl^wV+aZ{s_+=&2blnvA%bSt*l@ghL*mgr*3H9DxcxS42Bz$skO;L1$a;>q|M=i~q5!Rmy;+dx1 z25gQJ_e%372JwX7wovEDUSWZ`k5q^^-QVl>o(rO)yLJy$|=_HSDGf9!`8t9NkRt!Hwlz)Q)ClHqd_{)cF_%Msy;aSM|! zDkikv1ng{xj{u`v4^MwL!6g^?3U3g(s7=HN<@+H`14q9(=CXwfZSDRZU{h`9Yu9#1 z?Qa0CXa7@A|Dnc)^FbxC{9a*`{tWV)n3jF zs$S`PvUK0(vvMpN`CwRB^yYdx{{_u_R4Q)vp;+xJ>Avmd<5|?a_7r}SOGm|t__)<= zlhh5+RAMwTK0>H?+lVh!eW(G_#)5yKdL4((*UgU~FxWhi5^@&?AKp39JxP0JHQ9u5Ntiro|8JFzN?sa@sA3gY_F#n^+(6hi% zhb>j-Sr&K`E74;loF7n?!KJVv;@(rry3eN&v;b%i`0-|2*XjJTeb_(~o;WK%VV zdT9fk$p8%+;$wDOQTaFi;PXrwY8eQA|7kF6$-4W53Wv7Ql~e+Io%XVSHlEh6n{DH> zpz$X#mn~Ke?SG@mJwpHt`Xg8&npQ2o@4N40v087dBQtemBlMB6AIU8`Rb=xX$H>B< zpGf-_Dg`b^NC-aGA<{_xr7~QQX7yr8^p?LKS4cC74RYs)o*yUr2nD%U4KuzF{1U4n zAK5903M0(4_1GaAjQ&&QbGMKJzfDG99n2h^PbojkaKv>vI+pR+1pNr5CKWXEDBYpqjtRG4AiOFo4WyP5;x?-JA@p>;kg{6_O<%fZ zq?#c!75uq*0H!C)Eet^k#IU*}S|+=+;&&G%#5W{$dnP7ACV6~TL7?w}_u|zpsc~+} zNjaKMi}7OXtxwTlUu`o@>#y+@)D?Yi}-%G*8M@Eek7 zZV9W#O`>(;Yr|vtbQ!%+iU_29{P#N~@x1oIwf2++u!$FqUOewzw`i`C#%%r=MQuz6 zA`wCB;Dx*50Gk4`G-c(F(q$2RYv-9?R}Ri0EivQV&gpTRfrVSt9=O?$Hp*pCm3_Yy z%|=@?1@=}@(bVo6@gV5YnO6XBJR`iOQFP9rd~i+}b=ASM=~91sgZ;fgG$4LC?R7~4 z=leb_z;ogQ=pl|f!8@JQf2-DlS?C-3v*HE)u#1?F2g*am-(YCQlFk3!YOOQ6Nsd%XG>k^$2d>%H!ng!huiyp43piHYNr?%7x36;xRc?_ z@LeHIt#{L&Np<#+)maAuqk4>lkSJ;$W~!6DbHD1s>x8#MTYUb;w!}cjY+Lx-?DI?@ ztjG|sV;ru|CbPWTDIK)fE~o_=L-fw%Wxy(b&mM zSm{x^Rh+6OXd?g_zAb=1czZK`7~QvcoZ4IK691}OfpAq7<8Zw9 zBf|dQ`47FSh%Ha|*gjC*O&;d=BX2UiHANZ795|bl`q{H6uRNd+wPxmP zVg$DrERuYFIFd{UgFnMU#mASo7|5?u~(OJG}(C=R0c4KrmSA$@9_NyC(U# z%3&-bOco6DojvV5OXh7R1~Kcs8F={DKDY&2qLZeNW_81X#Oo8MCrWe=l5V|Zh<7L~ zUlnE=;zS-@XwYxH+R)Vro53U+KdE%`^^u-tctX@+7qbePT;MSYEbZ#1*o$>x8EN-E}i7_)%6c!%ZI$3&Z z!R1XJl$_3wozoj{!7ilT42>qp9j_K7S9^45wC*f*TdiGRhQpI_0-cArmKLsrF+!Su z{k05BdVwtIcSR+k@La-tZT7%lo$MEn_aFZIY&nIuQTl_p7I6C9Ov6rBDGgWqkdrj$ zxki$cuk(mrQwz_8x#z}>rxEl0%M|?hgUeT)zMC|f85aCsR{NMGTkf9ctB66&fAGcE z*+rc}Th9-290i3LVnT+?9wRo4z)KfTlQca}pRtiZNtH38a_j-u=~bu0684HAMSi6v z!~-1cT!gwbvw>3oD91GGw_+;(eD&jeDLm@KBQa{U5^@FKb$K8D#yzyNmNWoLg;3za{dCW7Re$mfKTdzT9sFQ;Z8IQd+&HG;(n`;6ikR!I z@)4au$IeG%11*#Zdnq5Jw`&Bv&O)zx)l?@UoR1ep+#h{xlGVOth`h8@AJi~nUGIIO zS4Ft2avQePx`@!!OQ+Q)H4C-c#_T0`M*{)H@e1I$^v&Eww*j?`*PlboNpL@b&O4jX z|6t34_M-`>gw9kYkEV+k;goGX%fYZAu4PgVWYdd<&_`A-H)()rVke~Bp_nsmtUz9G zqMhx(TAI1z#3&aI((R3}yA2<%w!$~M#8_1|RBw&G!;QPMjEb)lxdUJmDp*PW&~GXb zL0>OVy>K7lOiOrmWltBud!la`@kEblga3d`Ql}JptW_S(U=pwGo$NZ$WR-(E*_+V# z%$!zs?JYh=2(^~xi_3oIUYwJ7ax&`43GwY|)LmL>@m|=SZ`P>j^@)V!riukH(QebpYZ786EQ7fXhh7FKtDuHU%*`iD+jfxZNyFm zEW4r*r~NTPpTf3?T zN)vv(cIVupyWywiCh$5^Jrw{%#CHbXhi_ zRRcSr&T+T<+3vUoou}vq-7G_+tP!%H5gc-Ay9E?FJ^Gs#f`nWhu$*k)$$_~ZH3kSZ zKE;@>pAZv*8?rp*>rOo9LQCO$RqKuULDF<5d5gfXnJiGp^C?R)7G&-2^AD~yJ~2EA zanbLi>im}{LDlwYs_G~*o_yiWIQkt@5ZPWov5s8da^}$&{b>-~Q0}ahH%d(#S!j!B zcaw^q6QdGzYPxr4SBZTB>l4q8b%EsJbNy3m7*NCd2qvP2gbu$WUGf_|fb%au_hFrb zumu#JA%{veQk2M;kNX3X#Cwl}O$L+Nm^*j^^bfe}q#Y_FBe#$07c!Jwo$M(JSSp zgM!UH_YRJ{n==P_)0)esyC90#&A-Me^9I5^EV$Rc=d*M zV)!;7AsBKb%}>Y--Q3NyJXQE{J&7tt-)~|!9gn?J5w1+_d5Pub%Vsf*tFHz~&`+{+|Z&oU47#lm(u;{m+?Z<3@PWi4mFY%@1WT}+-G-q(CU(jZpn8ZxA z7EWllhip>Z@R!FSHC7!0?5;pWQvC{l-e|<@2Uuj%Jsf_#^*Pnw1$TyMF#oE!uesE& zI=Yu{vE!~o0mJtTwewX{s(*j7fB%t4QEuq+WeP zqGO_#b+tIiNkmXNmBjBvA+1&Z0xlqQ@nGF*Yy(tf?S29*G?B+xwxsIg`oT}cqKD6h z#LE|FI99mQKQA$b*6h*1sn`hyjXIHJes!e(2}+^ivxS^*@DW^J8Uc~O%2rob?l~m^ zAHDNVTJ#LV)glz96%hf{Ks?Sk zca?=V*(N5@%Qoqfkt*#p-~I0Qc4iOt`0d9x5#ayR3sAmBeD#IE5RM~^w%a@u;{284 z0A#)1nS4V67)D~FUzX;D!a8L&dgp!7?U}sWztqM}P-gLjsy~4Ye>?vT{{8dC)dEo* z$&^bXE10=+6uhHf^O9jrYn<+e|If`y%s1vi@=pcLSMrSrq=hQ95%3Q{MW@jVIxW z&)WFA0@f&>FPB5U9gd9kmWkyct^&35TyNxC8LGG+NUz<*Wb3~6-+QfNOVflwMa4Xe zhVpW$0QcJT!THz_t5uK|6WWONJAs50htFG?QhryD<6nfHz)oT*L_#}6uCeMLWudfZ zJN$$fm;|Sagbk0%5X`?bGv*`ZlITB?K+9M&kPdn{^QyslzxQc{%10ApyPFy(cI8Vu#NH_wJrL8J2xokRj8Hdh)sY3cA|v*p7Z2C~n!7XZ%|hI~5#=!jHa(dDf2J!DH(WT%VSzBrW0eB{9@21m%gn5lE!PhyEUyfln>B!lbG&9nF7rG3 zGfd98IE%sRHVAyJ^ULJ)e)Kc+s;%dtT?G{OV3+v~3>Ht&GRfVeco(AgVzVbjHH(ZM zxLFt*tc9-rRUX)!eY5B$HD#*F ztL|?q!pLYz6%#2SwF%_U={_v)$6=3a3PoZIhR_%3{6o*iG#-0T5*vQOLQF1z_iyk5 z-VP@_);{HvNK;=m=6SxW7WP$0s93RGyCmU#9;Y*LOtM=Y20tJ3Dg06|J&RS4L0hmjj zGhNdU2Ah+wy0RXyIEcQ)FY z=u`0P`EhnCU~Un&cg0a(uY6X;R?R$Q0Z zMM;L}Ia0fSOU&lXm-ry@&K;ftNIo^yAOe-f?GH+QQle^K|Bqsj^_Dgx7Wap3b5{Gn zN$d@ypmE6zSZn@eA59%6b;HLeh_AO1ZLkW+jFHKCe6v+nY?A_O;L-tfPl!;w(@doe zaQXq}an&(Df~-)KycwrHeE?ub8jXyLHQmBsW_e>%lsM$6&tra)FUIScnl;bcL+}8h zB*ovz@4U4H*Q6jnIczNXz}rk)fs!ToTydvCFLmi|XdHvpvtBNu`ywsn0>}@ppOK$F z>=}FOi!zHV(xFW^o4B>=(Zjq(!EleedJ!3)>1nFHVoVIOeBC5+)Cb|;_kW_$Q%El@ z?osa_ehx2vWA)CrR3o`@FrnUA?EDta+2ns6)G``g<{^AI&p9@jjs9nH@$qNKnwqI^ zpSkY=T|d<0km;PtzmHwWsZ3dGQblaEC%DW`yzM(k-v@{L&D;KVaf}yDg_dpJ!#wGv z3<}})`jKr#Ute7J`|BqNTTO@z+2;Zbkf^+Q5CNMkT$;NGk9Pc#ug`sbVvQQu#od^4lb+3I?yBH#ss#+@!W*LZ1kfm$qwp9Jv_E;e9H>a^;&3+#hww zsDIxtL#)aVqc@-p6^DQOg(V<>58j_YTk_wPlFj7px~#;wquj35y{@fYtlDDT&vvDs z3!_A#VPj+Qmw!`Nusb2p{XOk<6ak5*_t^C>&=P9ApHi)%G^6N zCPk@0Rlu@Ko_~fHZN8n!k*0zIZR3_a_ON+0PDM{*^Xe(V`RWr{t=3Uv%4Zc4et3X) z#TXjKUfyglz7$0RQa&O!RL_$8jmY)m1@y`CH-K*hip_Hxox;+aj>8bI_H4X$HoAchaf_Tll7Q$E+k8i+ldSI*jO_vZ`Z0 z8M4gKjoBKx6s0kGZ6$s6I)uB43s-&ZZ|U@pSXV-P%Kexe?4kXB*PN*)&y%_G_)Z+s zllG)p`Zf?*VbFs#K>#R&7+U=rBI_f^hEX}&vT<%^Mz<@eRx_Kz^SPu)3G6a36YYWF zDN4K`ie8?71#PL*R6~S%Q%UaMoN1k8&0m|O9e8v{OD`C+hE6pbBsr`#3To!K(Z9b8 zjo!%IdtcStYFY4cis8oSRq~k zL!zFFHXajil2xW)Y0kgPTTYC3O%^GCF9)(YTSPFw|AqP4dw=o;*cP(E9 z(*bcPWa*bcybWJB%%Kh*k9(z`%`Gf;8UTynsd&5)MBn~p;}3q6mz7^@LP2rMXFPL% zizl zTWAkj6W4ntmglyR9bRtw;Zro&efUTD&8gIKj0TO{)92ih8B4X z1zMQ4LaX5(5p;SiBvZw&)r`fu>2&_0e>w%wNJg9AHL}Gi*44H6xfnqL$*W9{5A6(l zfkz2v8`v2kQ3h|qF0Y=0mv-EdiD%;eLp+{!git3ML7g4E7n>ktr5~&9Aeo{BeC)7PicgFHQYn zOzUTE=HZgfir4f0^7gBjPTlCN-Y*|mawQpJ5U2y5_i+7%6E8SvytXcO)aZLPF_<0& z1c+oq&}O~cV8{LJQ<>g;dc5=P%2idzRJ0~k9V%T(ITz89KpR%DxmfNy*#wfXYHbCzweqs9 z4KRCviqxyoj?W8_%`hNCYwlJvNS|b|`v(5BqoJSCL&gBvY7v4ok|5$tf;N*~mJI*7 zKq6!~^x;?iBmE+0L3G6jwSh`hL|It$-&)P4o>=i^&HGT!R<(aWr1g%*&OUG`tm)|P zeaWBQL#ijqe?=bm;DgAbuT)aXb6tp(%Hb$f_J$JNqNkv{IpC10F%sl!sD zsPH72^|?<{VCwvBi5pw2NVfCI*BEk0g^yA4*73JDfyjW4RGeqOfTCTr1g<7c(>g7v z^^&|Gvn7NA)7-T*b<4m7AiX3lLG+&@xY{SX=4gOsE!PFjJPS$=wtt~4$SB!*h*Wso zMtG;1%aKy!gk+TcT=>lFhK0f42nvk^kx)E%p}~rFPr^ckr^4-3f!f&o9rblZ3$A40 z%utPq0`su#rX~)AlLL`DS%Or?vP^on(D=53g|+-v907__&B91~%o+Yfs^z7?FyD+d>p8)1EBXXsRyvjz{Tz zEbdIPwwY%@M`UeedP_G-Ean@+kkx-2l@vm|*aV0B?#{CF?vE`vQo?1ez2S;uf7_qK zKqw9cG*YRDZ*+wz5k)1vU*wOX&cAYC(bqF~gjVlHURLg~K6CS|^Bps#9M2ZP`QJSZ zzcb;bMcn*)Jo)Y--Dld71&4#80j=r-SMU}K(MA1kji==ZOK4UN*8|H!h7nGA!8Z(1 z%Rcl7%TR?BWZI7z4pw=MejzQ3upH>kAD@01?|z!z+xokr(NwLfXm{_d{Z8GcU7FW! z=tIjl-$9;Xryo?7)22YV!pSm6&+KpVWJC&F(HC;$&q;((ae&oL6)yD!75cCX(^f>M zJahRQe^4hQL?&VW>nnzzFIC59vEaGNfr%CZpAYa;A>sv-w2rD@vWx~XQ^|?%fC3`E z(E+c-Vrwb=2w`Ttacp7)Y_?TV0AbuHE6))Eo)`R;&GcxyaaZTrrH{@VB_`ipk$o28i@=s9jWid8Nu4$zzS z^)|Ro$DOwR_)q=VppoO!!V&j5Zq8$wI2tD3yk zPwbM4Z^+9euX?!@=x}P(ezTE(c*<4v%Na;8HSUrbVj9R2{>}A!gcB#6kfNTL9$i#; zp^mVemC^Zz3XC#UHIKt2`xG6ugm(6Kkpkkh%Mko>*xuh%u2mPaqj@mkow5DYc0BVW z_3_ag3^sqt1RQLZ8{{C}IdmWe7wWy|yu>)$$u1q~Ao1$1kQt3OvlVuRN>nvVO?>C- z*aymJe5BtLY?eV)T1(Dm?So|Y`4&iSv0hLA%c>4n%wj-uu;kinrN^I%o}W>;zKpa~ z{G}zKQvMlo0u%*qcfU!x}KuGy_& zBI*aa^gdzL^5*8*i3nVreN5JS;W}|y0mIwDs%?AfE*^WXaRq_5jkvmdoHk?RJ`5Jtzh7ya9K$y64MtE0Yb6Z=T{vyXQXhvXV_IPI)-=W)_8do#nPKa zeE0^J#0BJOz_P%=7^d!to88Ti5Wqy`r;SUt4m`H}HaWBDC~&?B{96{KpK!`)c-iM> zvU^K$NKGbB*Fm7?WJ^uRT~s0}HU5<`5zLPz&3%Hjw{>+0zezClPlUAE*>EA$gkohR=&Tovryv;?p+g{YkwQ7rz z^ZVG!u%C-o9OUnThG5&1bm>=V?A~J}5B}Y+{%V+c<1B=t*!Cvs1NZL%cnfK_zszhW zuq^2pLgZo3sBRjP0)O95s=;xN&kHODm9Y<4iFJ06@$n>}*po_kGAz0n%X*N8J$aT5 z7se`59yx1oeVM=f$4c$2*X%XvjPegvpl$ogw(q+W(9E5#+vhw>7q5-Ng7Y!32!9vB z&|b4BRpmPRc+n|Kv<5FpSu9y zk4RY#r97rg878;Ch5qD{+$T(SL}~`XoBy?%0{A+W3+6)8H?40tM%0>WpwF(5`kE_7 z(7$A>9`Uo4<>4vL5yV;ku;@~MLpcyP8Ta&^jfLMDP!PjnZPuOi@|Ndwbzck$)iFXf zPd^wRyve`D2HGp`h-V#x74xRQZ&}g}lkE+(sC%3W5-mXJAwOTCQrMfx_Nhtw83!(X z9eQy*e-sZn=|28k^R(R%<7`P|d3wWu-y4q)?nC(gvJAt=HBP@p-G1uhuj=)IbZ|dL zt_cIz|9=)7Sn!fE8OO44{?&HmFy_lmoVN^BL;;ZUm3NVxEQ?BUHmI&eqrN&IGGN8< z^u(7}Semmgn(~;puRo;#(F~!>_NlW+C?Qe2c+c_%$e9y5F@h{^_<%2Cna;8g;0FnOeGI^>o@F5yY0_?W-JSQeO@mY3sl>V57&ZbPBV|j z@LnZLy8Tu_-X-tpoWBDR=VUhqn?VYK&G_F&i-FJ$eDQ+a?^@eL&WV8HQQfHpTe;c%mFV14@+YQT&bK&O1}zmvxJCCvk;*H@ecXYxC(!*&E? zlUp_5R`X_o9Ud-;9KWp(#S8M@r{=#r9_eE6T~Do&_%~xzmwwMbOdT{j*rcdOZxp^pT5_>zb?tR}?Rk2xQMZ!70%YhuiZpGHjRLDS zx>y9O7u{aij_Ur@&fDRCjU2n{-6^zf3WNZt?G{z$-~gkr!eni)J2Xy|XnZ(Pj7KwmM@!*YPFB+z`) ztHgyolu)QD>>LV=GbN=s-49zLDh=AAJjhEd#qU0kwrg_L`DITOjzfx4Rp0C3Y(Y8_ zlQ~BCj%Lk2Diu6a-F3d@q8S$$ka+R6)3;65?ny7F&EBohb;T`h`R=frzLi54NPi<5 z3CKbM%lY1%V$`1v=Yy}VS$h+}ZqY!U=adXsST;toV1NcDXq8LQGRDL$jGR zDJ zgsad4_=%X2GiJ}yQ+EjNRm~pW2PC3WoVUcXFjVS&ce zu)SWuMG0f#OLp37OxR-kI5J%44xqoUG`Wpo`Q5N`P0;_yMm|q#a0^@NN6$rC_LWN7 zAIci-ToXip?7!se+K_!y7c7gnwIYn?uU#xr5G1=Q|#Jy8sN!^WP;znCMHCHq)@ zXDBaPeR1hL#~0{5u$Fg$wmI+Ya^_V~+R!th_ZTl)*~H(D&|=)fzG^ndjm>5V7B^13 zA`~ckVa9u6KA^yeEQZPpFwkwT#D-i|i64?w5$gUEYFH%y=4-)a>NEz6e(6?IEOdKw z79H*BHC0m&MfPc<<#hF%)2osF(N|E99UR%&I*TpNkH@c4lk<)d-b-^0#z$Lngf@3> zY-C2hl<_Y$Y^!Fa8vQTCzTrfyjDXu#LHddr+3~uEll;$jMQUFS0|hG_f!}=dc%~+7 z^=_nSdzA`H+l5v16`|DHV=ln@z4IMsjGIGI(+^mgzZLgg5#C=%_G9ZG4`zpwm+(i~ z8br1QqT}md;I4*Z{aTqFQOZOqlTgdM_^kM~qUPsWIUlg3%A}+k^I;NX0J9jg_I;m5 zM5JKV;&&J(P2KJ&?vCJ@V^u9kN}~wqU!!R(c-iPDQerh9y6?W&W!$;?#JGJcOG(?@ zBQ};hx4S@0?ih_9+5?Kpd34RKDsL561?s6<5nV_6f}T+AJYQ}i1Y6<+c#HbB+%PV9 zuksW8L}+RllTPaf>rUs4y6HD1kJ|e5l`TMI@JtyAY2q?|?jTv;+N- zOEQkc4-%#p&cuRnx?$$bLp@4!2Q#BzmXk#&1H*s11|ORGYk6SN>|@Fem%P=w>3s^H zyzme7lSGooljY#dkhTpu%-UFrtj;p#j;5;|yh-~) znkO!N-DG;9nl<7B6OmoM(SL{n#wu}^$EjXaZmLO6_?DGBPK>-M4PGfY9Z)5L-u&)s zimjW;Iu%*(AF-+VcDnTDzg6B>WXK^t z$#tI0CNMg{#*g^kDC;chB}0WaWD=Hly4wmVk=18o9k?AORiQs6cNC!?cCzxzu)Z(d z+HoC@X6H(Ocb6HeC^cRbSppGf-^lk(S)W7cuMa@_tn-UmB4>!brHK7)SFb*fcZ*#4r-ElugGayRV{Xe|`*D2}OS20Ilv;FVV2B7R>uRWz6 zOmSdgE(b~`t}yp$sHoZtR<+3P?kiI{rpWT=czYuIKE&U=MLW6;e zzI4Fdok<-Y&|eRG)T$p)LT&{v|GcG|lYU8Cctoj8e7k14n(fG(9BiP;U+uI zW8LE}ed@Aw^8p=Le`+uKrkDZng0ohu&Of+TQiW$tteFO|YUA!}6QQUAfAFe`@q{5m zA?~f%G*9-%@->+BtH=;+x1mebPifFo&@FaZXr8=UE*3|)$%yqL-V??eL=LO^t0N}z zHBz4bXCd-`38$9;CA%uvti9_g4S?TdNm5Si;UzyNX=)j}63IXLd`xz3I(g!OtC5&E z!$&u*Az(t7e9|OndPM2|I~~)Og#g07K_)ewnPA&%hfev`}X#8c5pQMgotKkW+T68~8GWPi!beI%w!-&wG4X zlp{VL6z9TAk%v@;|L^mYd{T-CfD=Fjeat(J3k*2sggfaIvBrnkQ$W-|njR zac`3J*GEc~VnGBKz8ryzt&zireAMH1eb&mc_NY9shRyx8b}f+XIOF;)$1+I@ns6Tg zArj{5YlIRy1)0X*;>eosFpR5RA+HnZ>0fb>UY!MRUb{%alG7ISCI1_xUim*w+@6fE za1x~~q`i?s*9+lB6Vwui+_2%vD8-YojbVtYptt4B4=#ac>XVMx*LxblI;CKjn@6%K zTJfj|UG`5E2#ubkr33J`I!aY3xxu%+M`z(C@I5Y&sr4t;*of}sTtDe9W7Z%FELfvo zW2J(Y<+cDlyA2+4viAh9nv780!lN2NVkB1fP%mvAl)$xDIh5& zIdllph;&HTAl)D_bTjnK_xPUke&@W$-#=a#mt)NH?0fIE*IIkuN^q+8e9OtzcqzE& z9kaL#Y*y_OM~_A8cjz@NE#Cg>ecayS3v2z|O?J7=0v3K3c23zJPaZWx1xbTeKwB7m zTQ>V}45_&|8~%|{R>NmbjKKA&Ji7RnQ%#w7c4LR{s$MyKjqxO{Io=O>sh{+O1}LqZ z-xuzj9lWh?XRCLlDE^ii(xV|`WpOQVTf~!89E;O^F2A_d#lEtHZ;;*u&`qLcwEBhV zjS2ds9IyaFi(=u?Wn64n1V_(W5m!%J>JZGOb7bY zx5-yuCpcONNl4RtN&T&QrVmKOZadU{`r@xm*~YT?yd(5GYrU_9PUMHrT?xDjz^VM< z%Z6Wk|7o-8MY5|j6{Jg50rd}tbvsZosPwaKAH7Ahd3P>jsrF@=)32J3CB~_hcB#Z^ z>hS}OgbVC1Hn-%ymCAMI@)AJ5t4?})F-Ulw$xe1N2V^IY*7QEU-fxZu0Hf^cjY}XED=>p&>+2EcEaBR|1>AU008v# zB`$lm1bs6`yibbqsb?|QG(eu!7_XjRQRZ#R{@S&vrzt;Krf|A@eAh5JJ3uRCg7=Q{ z`al5E|8BjP=1wtm+2VZB55ID8^(;OcYjR0SOVX7nt$~lKtlBO-eZ0z&{a`V=I{i(X zUi0Vf?DLu4l=vWE97$K`!?Ocurh!B5rJ5y3O-4u}?X3xDH~%_+rkl`-keK7l9Ov;; z78e3=b6d7y3?yNRZKscGCO;mh`zo|I>|Y$*5xX5Qnqw#ZBH#e(Dg%nh1Xnxhs*c(& zpO2=2Q)fw?;J=Pm26dqc^MISWS`A$L4_%`WEE1pby~n0hj>-kyh-p&jeyDfPLdt8p z*9RheTo?pD+j{jf{1PIAAFw=SeC`zUHHpb1rF2 zWOtiLu|^8lohxTFDP!G_UQwzw(>sU?8!HPK7k+wX0O|@!T{_L4-4>MF(N6Jlsr%idEte1`-7l_~0gPKOwV%(1trdRR|rian`3>thQDUTnn z?#r0bq;BI*AEXZr+Cr+^+&8rjJ&afjM688VlKb$6+c{s|LifF#Urt>~_A>dW`sSKj) zmQ%8&1nR}&h(ca1dy)LRdGVKvPe`-mBZw;sTu=fB%&tL}4DljAi;;XbAL;r#KB9>p z)MEuFz0NIi$%+%5_3Ok5q85ACsYtaEi^q+Gy8nguB6X~IRV~1 z#~&P(<|l-sq#dmM;+U+7IewspDdX*xO(pk;;!HEsY*{1yh=|SOqiF9X=8emr>@(!* z;YT|cNt(xJ-saXn?;ku2upc>{ytkiAA#(UKE@rmdl!@OsIcKF8f)-=q@!onQ>b|H( z5q)WuAvfqtx?T+x`pt&*N6x{W6#=GW^Lc=@77Z3Bs@^d$|1b>jfw<-$)V_dkKY&uW zVi@s@-ZuQevpjsS3{v|FiX^Pw5eoo5T46E#cU(`fk?Fvvot;VN@$=jVV6R@rJNawd zG1aE~GG`l6$*ZHDv?1jVPbb@-T2_CHP#hmGOcNLQ2LI*yzNCT`?lN@WBU<$JXz!M| zgTCo!2@8hVazmc`;K>Fg;K^HxUx~Z%OL%+GU#4AK^Bx;WY9pu3EziQLUZ!e)tNy*! zOMp3XF3wMsT5xW&N~}o_DbV;+(^X+yU;}kc#kl*)xUvEwPMs>+a;z~E}EsJQGJCkr&ilnYHa#0g=^mSM9;1fg3RL$*hZd2U- z*claGzQB*yf6yX!7N?3)Y*3ATW{$gke9KWI*Qvp~d9Ro1%_Om=P`d){w|nP{BRP!o zCLYwXCR@3&m|b@**Z%wS*KQ?mzDTUt8(0ftc7!xt#zoZ}SLpAYeEtQ>D<7jPb{wYW6oNvF(7y4FT%;~t}Ir_&=#)F zEqMnTXiR!&Ha|M$(hx{fKaRC9GYs{Y`y1!H&3p8#=}u04hw@MD+Y&#crzrOgj_lc^ zNj6Leh83oBNZWsGKj8H&wCT7xRmMs3oAv7bMN-~k(2KZJrh#j?MOJQp`C9ED!rG#vnlpX2>2=9s<`LyYO0s);<7fy;{`y-Rn zC-^(j0$pD+qyT7K!-@C}Oj4aoP?${9L!Sav^tsP!^56ZW3wb$k=HkwZoI}J^+C31_j1MthpU0~xxu0h+i{8H%1T~4ROiy4qk75e^%}^lg@I#w~{os(h zuyLBQd5S~3Fo4fxcjzc{_t z37j}8NEnQZ(pqnRSOQHOKaiZ?YmatckUK4PW({tS+#O_Wd1V+LS2WowvGq_5#YHba zQ6fJ+AV%Pek;~C85rBGz>;%V6e{aFVPT2 zr|;2()Wx0m%sx0V95r_Jll`X`TH;n*J_^=66nsikUg*w9C3%U%jZ=|Hu!Kwi4HF*A_qkJpQ|cQ|d4-f(_J>vjb}PYp?sPAevvczj<=(iA`i0iOUx#z9jx9gezelV^(&4 zTygP?RM}GYvDSG~g!z|jx7v*^wW*`fQJ3i$l}M)fel7IGHm@3yzy)5y(*+#s2JOSX z=F}VoQWx(%B_8GOmqtrD8k(LRF0Axw<73+0Mc}IVhC4zN0Oy~$c@b$t##N#*#;Qvy zJd7{0+s!|F=)rLDO_U`%N(4bK-EiB915x)Pd;H=nCu8?Oko{EVY&xwnsPu_7v9QF? z%oH#~k{5Ax>Gw12>ja7QRM8eQ3jU5O4Kbjt-pTs;54>iH%F@|gWrLQVG)IFASX{(? z;@wo7`blC{SfT8BI`j)3w{@M@yQED%oJN=)dwlE+=E;!M(iiK#oLmH-+PUYRnD$n0 z$38^`#Y=7hBlP3Pdw|;att&#izUsQuDvKj(X8xX*ZNq9A!A-g3!hrbU{18J06Y?u7 z+~b#Sj|{u3_RHc@`L&v-taXaKw*miPQ!`r9Vy+5yLjVj`&x%XAy3P1`3^K$%z@4$` zqler&{?vbz;KGmC`ff3O@3`JQep?fM!9OTe^4U0ys9Js9fh6g#*oW(a09k9+mIS{q z(-$-}@1`ckA>UN9HgN3pzErRx7l(5+|GnW?Rjol?_4E*6Y=O6X=5(O}NNFYWZra97 z#g+rIOHk?up4{O)KitTV)WyU&Kli16fN3|6?xi{xq$76otk~+(cteW|5q#g8*eoRY zMcxo9@O$=)8vOwuUlJEz;yls1j&G^JrNX_NT}J6tCv9S=^$}xyRc6)d$RmB05FeWr z?_dk~yWY0gU1#wjqA~~<9e{-Oy-V)>)VEao0gaiPw|d@I-(f)O@%=QAv9NNmZt(Zl zQ}Hz*nIA25;Qd{{QQUv(x4F)^l`|4TuL_`r?7bo@;Tn;eSnZ)h)bs)KjGLK`mbzVw zE#sc_rJe>BCvWb$UNI6_0*3l zSnbeNC*~>YJl^l)hjXUcZsW+FOJXH^aSD5|I9mTcul(W9!viJ>%g~DRMQ@;UvJYf@ z8D}+f{Stk2?65aiX8b^Tvw3%g`&MlEx?hdS;(!|vNYAD_&gAajw{>rr0e5X| zsT%AS@z8K-a(?jh*ROcz4$R(FVWTw{E3ZwKbwt_g4}kSqIcc^M22eV>u!;5OO67Jo z_ok6`*@=A&j=YkA>OobL&4bi)@jwsav-1iNG-iLpQoXJ-VBI$QaNlBJWnr9)-8kHC zGjwb+x@LnGL%48G4>6>Ioc3jB^paKs1GuBW=FAD3CR}Y^&T5CTK%Dxvr1hn&kB#`Q=En4pO~omiTsycC&ro>EHr&jjQZ~1Et34_trT%Ms)R37d(t>^D|8L^T&eO zm*2b%?LPOf=-g57ZjNIplWV4?GUNylK&C}INGShw`IdQSX5iRcaFrh7m4WYOZDR&1 zJ^lt;pRuN$!;TKMiZ)-mzewRwH}ffT2C5bWZR@CC;ZFp6i>-OSCsYuD<*Oy8S9UN$ zVqRenKE2L%DLbE!Aja&j*piYv80=%MOt@r00tmERnOh#S^XS7Z}mL z{*m2_0ygTJvYsR80UKXg=EP48bBu(~*?ik|JdPm%o7zUI6t4Ce0p=Bm)xGn1FZ|g> zNOjxwcnu=Mnt-^=UxW z)tmDWV}i+{&_bKEA{$8Flx&O7$KnEY<@b4!`j>>lb~pRyKW#2>HtKGYL3Y&DoQN(t zZ7K+p6(9${9XPGwN01W=@1ot}Po7PN$rRe00F;{fakdowVmD77LxU75CLa)t$8vE}y3mncrk0d4x-is6pu@@5 z^oRR6G!MFJ`MEfBtt;KG3s$+YI44zQ(VI|JF@-S^RF^4n5!c0vv&|8(?kTUqZW@kF z4yj$866H})GW9r>-ySYG$nZc|{u3)O>7rV>6&%%lr@;%a!`&hin4 zdzPP%u)3Q$D90)sXC@$cOWdRKzL8g>*Y(=gEdH9H48ADi?&6DVa=s7AQLRv!N*~D+ zpry!Rp-bEt29U@)!)H?&3~}RUxJjgYXa#mmd6;IOh-Lja)CqrUp~Zkk5MUIX zOaSkID<(ZSWOy%xkpvjCrPKL#sR9tK%sFZ>y<!A5Xm4NM%szaZyuVMO4AAteYXxw$Ob>91Isw*sj7{4=Qx zjlZm}z^n*E2n!`ckO(f9KTq~-$5}_ya7_FVLD{P?0c3{#?jhHUq7l<#&~7Zg2q1pt zFHHT^G{;18uPR<8gLbpVh^ldKSJ=(zN+kXq>wP-emD$HEAGyI?>E8F_*TIl?VVZQu z(ILWnnTEH!4-dr-?6KWQJFj;cZ?6kwJ#P(XsbIpBJ~@Ln4}{q}muX_rX2*R;s9r}<`B zVR=e|O;jpnSZP`_@vh;`qb-@H+gZ9S-P?f$lKtewh2W~7R0zjkU>lrBXiMuBW7wbt zETVnYQBjne<8{F(ZQ`;CC~>RV@$u~~CG9BwGFg{dRQ@5yP~S3O`250y4I`)st?z=p1DR$Nvg3-nK`@hk9epe{KKwqXK#dTbS?9SJ3ckd1u=y!hn zU0-k3TE4EUHTm%_6&Z{Uj|=%FO7}EfUrK6JNY!2U)J0!k;8VTex&O* zbRSg}lT#U#6FSIrkSxP2wH@ZZ#_HS!szZIeA!XE4F2AXnj(KHPi8OV}H-rZ6u&9i9 z5OXpb>6US;>=C?Ux+1gfI8(MIkF+CFdJebbl*C-&OUg7tGHzH_(7KIq_KaP1B-yQd zV4ROwcUeKt{R#g4gCajDi#+P?m#PKBsJFq1j+l48--z}ascId~smy<&56oO7e{f{$ z5&?Wyo!#_fto zDLEme9~*9pS@Nip{|7?hDt^SP->Q3#dSbX~^XYY+tmhVa2IY&XYSV~CBL_DWGj%|W>>mxTjTXqyE(>SC2ah?;iz(yc)n;UVuEP%+48eI)i z4^)EXQ$XtP{O1?@*SDliY30fX3vDh-`*0m0P0MVXc(PGb7`bxGs-r1khBit_Cm$6= zO<(>5EX0dHfnEA44^+xl0v0MNJ4~_5lqBrb5YTh=8nw`GKRzVVHIEc644{O(xTEsR z?w1(Ma_Fh%V^n&?fSkQL=K0GXA;{0Y^bSgvx=Dii{pf1W- zK1ET>!U3fbUo`dK6J*AHK5-_9_Khj7OPo_Bstyt$Ncs5x-{t?jSXC8&PQ<_&^far0 z@Zz{E@an)Idj`P+OX25i&3WjHG1yCTAYpcl+wD88n|j6QWfsx;Jc zm@IYJ%Sn^m_qq|N9%`9R^8>-jqr$&;S2My*LA&&N?IW+<$!2?+Vt-($%A=(?*xG0$ z-mfiaESN5W-`rS{H87B)8W$^W-u6iC%F9bg$kfrzXD;0C-4`*I%r0daN{B3>d)iS` zAWNu_5B9br`MK8>sqi)+9i9n0lI46`ewY%*|PAYO|u` z`z9fR9pL&Efj*Ka81wl|CUM%h7z0vM3$p&F2*{OysWoVbtVL8TiN!Y`*}n=+=qAz9 z1otz!=h#g5ub()7B&{6dM@WN5bvHHJwZ4MvX8D(Um>)J+F@;~_KIK_h@L9a^54rsS z4@b{)@Ld?lffb19k3dZO5mwt&sba^tud%Nz*(}c;dK`RfWSbSJwhgjc(>|YU4sFf1 z50zYeLlLAb+6)(UN8;a>Bdqq()ggA_AzCX#nHaE(6{TM-Hi1=9#XO%W<-b>!8L(={@awf#hANS;l=W!M zpJP43sdV{oFTnpk;y_rm7UJOXFVp&|G7nm^`+S)HARM0QOP(g>)|viBS7b{=2H(ue zs!ZZVc_#11$cc(lW}l>`!4Y7j*OAc`<>8HSmj!OD-zk=C_)&i2C2X1JBONB z^xeo23g`G8b4)&%Cx_HbzWY$KjQ0V|em#JUmR+DLYc&+L>{$&=g@PxoNc$}4IomQ{ zVhlh68o)ubk>`O~;w;3P9vse4D8uLPd?&&+WYB@1av**;nRvXVUG`A{o=dSNd|Z&8 zuy^!ci&gxcE3vN>Q6rm9v@Dn!NEsYJ)k{%Y_#91|VEV_5P=Pm<29{0^7j5KR8@hQ!D zk$rv`+J;>3dN+Qvfd2+~8bjUMFN1z_vjpcg-p_AY5Nvs|BJW(ww+wN^)J&Or^ba}= zit?bC<2b5i(3CUGo~q3nkT1}OO(_dVeV;3tPH!AyMHx^%Ct~Yc@8ibDqtbhCRL@!6Kf!Ffj0A_vqs9>lM2K`S6CPR67HYeEGoqH6gZa=rFP z`RyrHJ;lOj@yG9UXEagdKUrr?f6!*^X9)QyDQLqxefx7M!v0Ub2ObD(B*96dLp+Cn zgvYAgQl?laMW+gLsNH~Dcr5Tg6?(?lo6N@^5l^(W4tyS;cWGq2 znQUzJyi5Z1swHuc!NfA1d(pdbjF3NjL}_2RP0cZW9q zdfT8Atmr56UPGFxJtR_Yhr{yxEGXx)Ko@42E80B@%s}wT_L5OTEYD0MZcvWqS`Nt4 za9HIhEMjia$*0-t3Mmj9~VQS23=0&h0HVe#*T!c%4d-U0R% zopvq$#jGf+HBBMXdc2`t=$2>0^D8|Y?@dXM7H?^bD-~W7&f5~z67p96uJQkI2o!}t zscxzCmrzVOi2G>xAYvTvN^<*?POQ+?y-r0B>1*1-$px%LMWN5orb9(6=yu^<9>43m z2S3Cfd3v@J2~+%(p=MEen^jC6)eHoGhrIJGCE@u?jj_u&H`vD?>Ztd=g!1g=C8pZT zPk!yc+Rwo$(GU|!|9Pd-`8|{W@&)#!;_{;s`fdy}D8OW0Ztv$CB{V?TCus~21alj#LwCpA!)g}Ka-@Cvo= zTbG;&F93D~BtP0Ee>Z5DJI#?*L!Z7c=#T79!eWD8T)#^r1ES*_zM*~)NPw<(L&~ZU zNEmfgln?;iIkUdtL7dczj7(M)zl4uhF+(yEpPQOo`INlgI{DF){Dh&AUt{(nE-313 zJ!gBRp*p>wV?$dTz6t*F;Mre*krniUyH!~t%-e#ya)h^K(4!k%!)7tV!{+8EIFEqEBjg5S)uK$3mwf@Tam zD_+op35D6^W`!B3ggM4gRHHZzM_kyKh11M~K{DRbBYHQRz(lS$t2B5imu1!U$T z!~|FAzMym!?XKftQlHJi3g z?ovg20UM*JVm_T!Id5h3v}}64A|f*DYH#r(-cpxlKY2acisqP=X5e2w$KhX2X85fC zlz-V{ttKC7!OQpjP$xo{1JNwo5sa-YN^)PRj`(`uhcS=$>0&u$*Mf%a;aKLC{<)r? zRq@TsA4gDY_hGKkc*2cTa72MoPU44PJ|xn)PtyS%TE42sL$O82CVT@Mirx=GHqc+&NOOlh^+9Um$=zTaUX(iqBn219M7eI$=sKl=l*f}In*ftt#QJ2Gol z;%ZaT+B8eolj2&H|GXAac~?M1<3%}&yW?DrNO=6aHof9M#DKMGt1TcH3u(v=ankU; z$i0%;_fK$`n;_MO6E}kzok)eX;V`6D`2km*6=QCluHk+oKGSeZq3Dnzwjz*7Asbc^ zKo$^y4sQwEVsI0T-`~>t8FXygd(HTS#!17Aeuyx%D4((Lg2b+DP|jiWdu2^;wcx_V z7n%3KqG9012%YvvRM#t~(T}2QrGeFoMgW|M1Wt*vXnr6j@%JH*b{6=nktk7IL1)bN zq_mgJYUagbQD9~&;{<*3c*aDW#jJI->Tz~iba}voFK0=Qwc$lkOvD`j2R8wI66}Oi zvu4{Q@Vo!amgmewY4)Xz{V4Ld-Fwg9rp4DB|2Y}rvb6&OwJef+FmrWPR4#LL<0tiP zm>j}cOa+-d@isLfy&`8~X)Yw-1bU5Anh-#MKDvB!Efg(Helcl6{OUaUs(5=41K+H_ z(8@U5s>0OlWiIo#C@!Wx^`uVtSF&6nk1C1`u_?gF4TWhGK575H?puxCR#jYhGLFnO zgUeZfN`>yh5{V=}JF5M_PXQci-p~Il5fsc}tCfNV38WC&Sih_KW1Z&$9X?4sr;=4V z$hoQUJn%XGRD9UW48;9swd8f~zrX)2A+i`1(HClLh4-2ACNCJH-|n~IN(;J)$!2BU zRns@Vz348J+!8;csSJk6z2;;*4YwlyMxjsG%`x>hnOv1`BD$uMoKqzeCzGndE)Fv} zUMO*63no*mDk}`ip=%B%x6k<#2>Mp181$Vh@={7Oa%p5$e+=~Ub~*8^JXP(pO037B>5&5cjAcN1QIJGL)FwN}&K!!0~AaLn&(zDn$C2 z8$%KM^6BeVgw^+`2&?N;@#7uFdZh)nBZeP5+p8dLdxX~1z0!fq28A@Zhf;1RU!XZ~ zQ;K6oSpNwoID=GSmWdtOh5<*DCLpV-PT}dZ8`8q7{$PLBj+hi#z2!}(-a&&;D7!d!7ikuVb0A6OprRPqvUA_01#0x#( zC19~5p+NNvv(J)sQIx}`WY)Jc-roP{Z6d(0&GYXCj_F*gYoHCU39X)wO?68+$A}zr z)tyO7SlTvyO$kmPrqN+tnFCEKe z%$b_)^|H|c5o}>u$et`@WS0r1x4P^gV>Ur0N15F4L(A=S!TZ~52^;6Z_hRZ!fsqr& z(Ti>VVtW8Czr!^oA{~0Ku$)%*Mqo~Tga`nQBiAQ~F+NP;=8=!V0AkXEp?aHQKqfiK z((fc*5q9qw+y*L-Oei^InHeJ2Tz-qIJQ1P^r>hGx=TKG|dbpZ?c+b{Cf{z?!e+?2C zP9vyjFmSYej2}O5mfS)OUu)s65j$4;p zh$#l9I7+W7Ii$>V$9a2%G|0nd;9=#e^7d_H>vvQ~{1lxaP4`q_40M7&Tt)b0`=PX= zUv$xRL}#$`8mmz$NbSM$RPNPf#M5ueNQ2uiayBaW&AW}swY*0i6r0^f&+S;Gm~fHJ zS#=rHScyBRQa|pz?H`{gA;o6$f&Z(G`ZtzlZ6bWRTbaargnyq+yKaN4E?-0Ej^ZvI zvK6z!r*lqakNz|~7?@Xxl{SAXYQL<S=ihBnY2)=OG`+@H?B%yG)6}K1-Kx5ecwmlo}^^(xzT5$z(97SoC zpc(Op(wk{k=G(a&d$W||=?lXx;F}#4IbVcoj5}cc4mV;Gzz2$`3Y{zo-ROhosO9f` zhKQTa6^OT8#&6La;&R}6mw@BNP0xO|dRHe+`PI9t}mHE$i-s`R?N?!V>BCTy*~-bv&eom4gO11cc7ped49j z1SRYt-F~^h<|75TGhtxe1YW~Nh;>-IGI6D}!+o|ca`}n3YfFr%LM76um0P#Vk|{TF z`N9GA@A=Tym;I$=tRSQurbsNgC7agE)3;oX%tAf`y1ReibHrP}cts{_XV+)MTx9(t z+f`<^D_U+jfZ+`TMFV*n=KFY|)TynaEQ0bXf)euniR@+M2(WD+{jpagKaCjFi(rT; z>XTg{8TiJy0SqZp{!)5qG8JbO7I3@}z-j+{fOi#VM&S%hetl@t=YR}W7A@X@aV0sV zrRs4q&U>f9(pH#1(otlU;t{*=-QnF3dc9*t_pY}TJd(%XHl6lp*Yf?yI(M{Ek-f!t zP15RkJpTvke<2G+5KKg^wJv}|HLXhrr2`MXxx=q==cLMY>S4Te3yzPa<&G7J3_2*p zQy*;mVHcPyfL^W@zMM(&?JDX)_ZtKj=l${sqwf-_$fHtwx^QPWT;mE!tJ_sJc^5nC zdt%}}O#Rp8v_9qfE>WCYB!0jfWE)<;**eyyvVL+EwyqiGo< z6>j)J+GLwm!cVmJ^)8dJz}v^~P_`lNjop$RgJO zSbQh@UG$5tP-hEvt94LaK^oJj`~GnM2w#$et+25VzEO1JAZ*8+#ov4Kv-u^{)QJE+ z+L26DiJ%E*^tiKl63x(eN;KrnfYQ49i7H$77M6A*AUnw)XmQ#C?rxx=G5+)y=Lg4! z4S$6_oxoCv!p_&*a+E(dHO~PMaCV;7RNlccLE!byl(j;<^iA@;gCTtrx#L}Txg!s` zVVyf>Q2U}T(cKD-H2*mKeca5XuRZ>F9MS%m$wh?2nh1fOo$}Yy&Iv1YjsCsu^A*bF z@d0uAHi4S_1L#k8Q@%^N|L*7n^b6Es>r;MsN5ek6ZYLbKN-=1D$;F%gc&+vMiyrAM zk~%kWw)`8_($S-VuzVf%PN(-qx^RPtu6z3@G;ahLbI6WK5!YjmBb=*vCWm{xNLFOB zx0xYr$raV03Yt{5zboETmZ&;DtKc@pUmaSU^3wi3wi+|MRV_?d1a1I|Ou)O7!oZ?9RoIlWd9SrQQ&+S+0G^YjJToQcS270$1bReQEqS1MSyGm9 zlj+;1)YBmQLquhMnx^$?-aY>ylM7o&ft;6Hr+@!dCk)trlG(`sSWQ;+8Aj^mrVNtZDKPXUU@Dl=?O1x-Io_Y6GW zdAxM|Ci8g6HWx=~>qd(d7SJ|zJF`=%Jn%{oqxkMaV4?L3!x2sgR*!`4IhYwDr z5Qe7dw~L;_sL3sO2kn0jGntdhF7T?f#)UqbbXAZ_$rQ(f3CQ$!Xy47uI?%}h16yXx z27>Jey?KwLDIwMuH;U?ix9{&jZvoQ)iE=J+vnUWjZO5w1LZ1#i7ZxALPxNwkyh(`i-<^mL$*Aq5*G=?^X-88SEP_kK(HwBY(IpD2Q{CyD`&SFv zIryAI9k?_RW3buPetQ8oQ>)OU|G@s7F*17SlXkP#)g}EW=;M{tiecOyu_IKnAKtd# zvr(PyK+az$wSseN36zjUw4&rPf54C($C^qV4*66Av)GCu6`UO zrp7$lMkL#4+uAD(zU>C@>^yFiF+4XUy1jRB+LE#7Be|*5 zKZSVru?{MnNFveww^^N=n37|W@`9T%uoUZB*OWg> zfV8_VFxL58_LT}(p;Ok{Mb+}fvn)(z@fU3&p0o4NrG=a7WE#>jpXjboD=?9ljyZ?_ zk0)PN0{&t9d2n~HmHCl~9v%#K$2e>jbCBTO=|D;j!T z4%gDBd*(3Ob!Vd3J}+Qqw$lK=is|jUPmO$DDk!zmKRwTra>H8^Y;JbBZFJn6b|+DO zL8%T}IGq^})~VsJCj0Br0G4aBBE-G)nF$JO2Db%@*JS}**TVNo3LkY1*S_u0^ymkP zx}{M{!1NaVx9CC{7FbFfOLM_OW4?tNFfNoN(~}9mk^1KL70)CU^)bHF847xOE$@Y3 zf^Um&JTMAAn1rke61p7HTTB#?A{{vFJL^@9O^j#jTKLP^#pYY-Eb}>{L*>|_%)!Ve z+^fPS-4vd-;~NzS^q8K2sfKU((L}k6K73kXFL;iE8CLVAn(JUtMI-PGIVqfOp4;{Z z@(tbn&X+Rw;9zI< z^nifvZy_~}_bnko`Iffg5WSgT4ImAYv0eCDce8GiI7*6!GG!%InzL+{fcZUtzwN|H znVJrpsTxvvv%9ZcsEromMQAez#M1(_c{%*k!XJ1r+;=QQ#rULeT<_d<@k2fQIfQpn9rIx5 z9PK>BxXS28@t9Is)C*vZ^TFFhyogV`Eclb>a!VOb)Bxj&W93{#oF^G|F>|zI9n-qH z7;8EqXKK295!XAc_xKj@Y;Q?dDUR&t7ia{XO&%<$M?yP3mKPF%GETw1_8nxc76m&@)tKwMzIDa&S+^Y~hg6Dt{_S;kS^!DkGN4kT+?MWu?$rqh_(2S#!Gok5=q+9kKSPp=h6=&VFRSNwI!%Z2vhZj6og;T? ztb$9;xZmwLv!zjx;`5sqp1$D}OJ$Lvj@x)De~q_5DP@iux(Xw1o_vKf}OC!<%6j=4{PHTw*{7YzP}>48>3QBw_$bWH>%~cm>zz+ z^5%kO#Ah}=qzYOnIiBcnQf3e_<2)2PHeykDPCO268gf@n`B<5!!CQ>*qJU@@;-{FP zIDMgp9+U!&gmhJwJbN`=+ca?Ji|vpzC`W?B%TE^KBnaTn;R3J;SFDD~FBCgBsydUcq z9-n+0W<0rS9$PUvT~4})z2q^Dd_h8K_#_(*!@jebV;EY!Gg>=YCW|bWT#k zSVOGM{%VE%vj~3X05QWlPhoAT3IBnb$LA#F`)@g|Z+bQ6cIs^mv{m&0n%5Mj?FTyn#EK62fyFU3qm}0codmRaICg&M(bNV zz47L})56o(KioQ>vY<@fm-QCd`mjhy;IXEi6Sg2@xFgt7sx~eajQ2Nw`RIQ^_!Bs7 zCz8e#)=nUSV5-8gm!f^*n;w*}Z=QArPXRkpAf8KaJFapPzULK^S!nPlIRMnhF*@Xp zS|l$&qxD+c#^JXj@9foOe4tV!g9clvT7NHpc5Of8se9pBCi2=-pnkz~^574!JFEqy zt3N!vKn*8j$5Z&6`QD4JF=t);s+A6j5%iE+$YSfrhw{WUqSee=N>rb*?iHgQobmvW zE)@XS^5j}~k#(x^7P(4(-Z{&QW%4N1CaUciZ@7Sp9ic+h_cm=&qM-`>HnB}b(HE+> z?$@$)cePk^resQB%Z_%%ce7`lwpvlLT_J2@Dv$=@nDUsnO5`(WXDM-+`e8D39LTOm zCHWtO-Q-WsIq{j!_eUF z=Uc!d>5T`K4iFmRHedDw}?sMdIi*fh>JBiMmjh%)}M4~ry%o+D~`UC^A920&w5r0A8B7@1f7&mkj4m{5Zdkovi0H1(`>KN}opcMzA1z z{F|KIMPKY=U-dKr{{l|_9n~qeunjayth(8YF4uoJe*5!oZ$Om^QNp*Eo;VB>@Z~Hy zRxiVQLyG7d_8Bx_tn6zYdHC012d9uJ1`!F6%h&V#($;N{b^Z|}b7uA~-37!N9s`1v z>d{-(fT$x-R6GPR0d1v7XVKL~`#E2h3whHj#uR1I$G9{3^i_41JQBvc#2HBr(o-Qphl6*8ahWt1-acR6x$+xBuOD2beO7Xl-hZbgp8xxR6kwZjcA_EPpE?F5^jNym%is z2n?7;OOt3DDa10J+wZS3dh(ax=8Sni2!r^cggx|{nER|YeK52;UL7Co_3dRM$5fXB zGBo>d3cOP_L-Xl&+(-Ntor`_6_ZM{svV`@H8}CwtVCcuAZ=v6xiaGgIUTda&M^aDI zlx0xCDJAkei938_JYJl7el!;gJ_fFfI%&$Xh-#ZC4mn;~BimG1_1pc-o$hn zzURIjRn0$Bb9p0e7L~mH4VmkACEmC|wKM)ZHL>Dwf%N+f6x%1l(?0Bx+gFj4j}?KA z(Qf;nsB_qS1R-ec9E$%U3t8<5IJASQ2|IVfs$)QQtt%`{ezj4Iy!`zoYZmoo6~(?J z{)mz3GkbNW>&Ssg>oq&#B!`zd;8btipp+E9i)W>ivwGbPsFZ+T3gi5Uc0ysHgFewq zMap@XrakY{@7?&z=!)H|HYzu15gDt!r}$!3 z>o&^oKW6B;_&7+^OGEF(Ej^8w@m1k{qS2jv69e=SQ+?dqm6y42QkcNkR!X_He3{js zhUI?5LA8T10nGm6XS-)SMps@-J70Rl%-A01r%{b^{|H)9p^-azj{5q`uc^E#Z4g&Q zoz6B-qE*_9-d29)4KJer_h6jwL(>_IGkZW}3+b0d&5LetzjbLOJMyD^@nyRFk*)C) zd}C87yTY5NpS<9luDzKUFYSVY-hZRhJ7KMwrUY+jNQbjdj=-x^3oYGVbkF-$7^En; z=0XVM@KcS(Ppi-GcK34^+tU7wF06>|Snyck%`E6dazyVULqn1t#G8<7`FOJ45>(}x zCfl5a3%(Lo=i7zpv|GqkT%z4q$IMNdQVz8ukHOrs&BC)Q*(~8+wl*qpR*7yNB5E8PGzf0`BtGwnWeSFlQ$jB+y&F{!@&dSwXHX*{9?E%ckG& z1*7Z*M78^qigM=~G6PDip~3Q@?=+wd90uY1ge7yV@5bqM&MFefZ0B*H$& zRpMBBw~Y!qfQ7a*iw0QxDWv1mEFBMY(cH^ENEAN1&84kq*IwBdL!?!7HsF9C>lP$L zg%*5T)qh4LJXkU21mZ+gBeE<0FHX#*xK+jZ3y?CYLd}2U#6M711C?&U4an#X8pxa( z@5A((S0A}bA=XWv0ruWIUk!0n5HsP$HwK_V!0!uai6HsgC&27U6UJ0n;1y0*p2$$) zon1hMC63HA7@h!@cOHKSSZL8g(Nv+_*E8FUYRbavu&McgZ-SXG&Ylj#5`GD-yzOO( zD#2cFt68ad<6EfhM{tY4SSxyER&V++Jl_59POq}~4{hL)eUiTUA)dWWoA zOUU@r+S$JzO~o3Qd`7N8>*k$X{3wiO=0!n1&dd{f52w`s!-~R8K$c3m#EIb16Y z5&g_-F;B5I>j{jjw${x26OQ4%3u5YQkM*b8AOYqpb}@)pCZOl8CshKS2$0;C;#6;j zRiDLskCpaJMI>NGGZ^3YrVR;Z;OKOHOeewd}T?D{{TUsVe%MUktloGp2|i zzq2$mg)U6x@T@O6CEy=?tLomVi%}Th8{bua)#d2odz_YbEnJMr1xK(K(rauQUuZA# z(eAk4xGDXdT394SHDr+DQ>zfze?QnaHz`AQcy#q8u{ikUHpNO?S3K))9QjsY5!GW9 zPu^d6{3312o+QENce*=&SD>*y+3dY5k~^0Btqrxm&OMNdtBr=XiW5_QhnlY_!o8)< zpm{5=+%mOY7sDJgXWzHHV31fk^O_6Poy|X7_-0`ua`qPs=s3a&CMMdyZ|Qq9UlV0$$b=#W zl&|wpnyFxR+>}evKYyH`{);F`99X#I#qyn0FH87!k+5OsuUx+&qgb&Uv(=wkLys7V zDyr_zFQO@!_?ZKyOlBFfU~UCJTRtjxR(uG{i4xB8qCkP8767Z z(6{_4A&z)S>g@k5tiN#r8JgH49w)vfY)OW1RR2G=zB;U`ciWoSbV_%JG$@^$ zMp9I|8>B&`yE~MS4(aah5^3p{?(Pj6zU4XheD^-b-+%ak%6`{+=Nxm)F~^KLZkOq+ zr|~7M#PAqlF`$h}X8EwdJCBC@f|0ph82dUHhm9kg&cQ}3tG?|k_BKM0R7c&5N?Uo} zKB3cW4*HZcX>>h<3b_q5^b&*5VV|F;m_F;no*{jCFf&i;(@KE@Jcv&`ydUZfr9i3o`!?;rRRBI*tCUIF}2#gJ^THn%x) z7hPRvE%q_xGJ;%Ahl`8|^NA&2iIpkgUyPI=GMzuz6{cwi-VgdMs@EpGmNQU{;k2Sg zUx|{(<=WObwlSJKutt{BFTdy|@p_oR?tnK|z{KL*BK3jlxm>%GZ6~Zc)5X_CzGR_CV&su+kz?=*cm2-DN~4xdMzF$^l@n_|Fk^bIGWS!l zObr;usCCf~qkG5xNqcULCLJRtu@aL?bAz}^)EQIvH?5Ybo!Al<8mGcTXB>+jl+YUdBHpmw-3^8aku+;TOwh28xj7%@bG_YyPj2sBY```#hg2+=!l1PbDhjFu(3q zp{0Ox2LzTKVgz6+^qxSwd*0m^_*e)S8B3CW*Y1}Z-e}{om0g!pPGPGJ9oN_erUBP4 z^yg-KRDUemV*(W?>XGOMijTG?}+dLGTw@pq{K#^Swf6iWvao_J~VPxe{r(Ny+tU zWMD(hQFblOToN8^@pqpp%x@BfPSV@_rcaCCT+u%%A#n5evMr%0nHLpZC*`|F3zpY+ z=zL{~i4Lc&eEh9?lZw^lLj5VD-xFuqN3NX&K+(#tr~hyTF@ zD`FkYw+%Q;hg9MI9b=~>vheq7r_rHAkM0)inmGLcx&n9vBbF>y#%VX#%;nn%$Dm+( zH5#)}CcVp{PX0qJf-{M$I&sT|u&yMvGmXk1-%BiHnDqZ-BL1Pa-c77EQYv-~csjLLTZ0i9F7=^0y1-;H%^HfD*1 zgu&@#hq8sTwC8@;m4GN>B$fe&OjuhL4(Tb8+rTe$Gf!RH;3s;*p88w;IKft`p<{Jp zG)ewdxUub^@?43h_xs3<6a4YgQ<@KEF9Fd(WSsL(R=o2-u{`p`%{U>jV^;(_I6LQr z5FSc-JvA56G2wSb`-eo_#fpv5ak3hhK8q1SnY+hs>`;86Gk;ljY4X_<>jKor=Y0I~6Y) zBu(jwgR`ltux5}8XzCJAs2E4mmnc~WLuy+?1#LSowR);AYX^Vd`w-I^S%-g?;7IBl z0+b|DP24j?B zDPutN z(R=vpBO++vtKg_xf&_G{W?smRUb`q{s~J$_1?5%i-&X9eplMCE>|8!vH6uog`eAt zTYCX61lh;o2Hk;|uzvb=&GQ7=A$g>QS37p{4mUyvs6`=_uM zjk)vGs(qrcfo1`{NAQH#-*4pR3!(_%jl{KBw}f(8-98}{(V$%fAxRSy$#?IbKyRh@ zp{_&Y$;Ra59PI%iq=Yy3CfE-T_;(S3S^093Sakx)v~SK?{DOg7!ZFr ztl8J0a7Q1-VoO4c3Q4q_d;}!QM&qKevuSKvNPrPUiYR>gd>&Zg{E6rQav-wSlegDW z8t?L09kF-t>;o^D+(}GgVfZZj{1~0f!A%84-b)6z(iB$SIQHS%Hz!%zQ{)!%)^v;Q zvVAr^aYd=YN$b2dIaqn zb@Wk%zrh8jzuUCMdlvsd8Q4Sy_L1-OaDVy$w@VuAEEa5R;rk>k*BZVvF51g{{hE19 z6X~8uA-_CNVX{USpNoV7>RryBJ22DxX>Ofr)zRO(()j%@Ah8iW6s{8Zidg_Ay-~ct zwIsbwgAKy9l$ZIo%5EXJ6**!JRgQyfWjezOg` zrslrvD(YA~ih_C0Bclb%3}X_mS>|lqFV?-lt;2x%6})#D;W=x;`T}pB1IxA4nWxl6 zwbZ{=i@MA{zNj)KfNi6p5 zm96GIfMWq@d{A+gP{ff`LO+MQeECee2og*`24DoeqgLWdV9$+|LNU@htvM= zgY0BO6z+l&9J;Xe z1L=bUFsy&CJ-&VUIG$?%&vIM9GCP#`Vx6YOFHA-iN`05znSO*4g&A!2Jb@+-z#=>z`olcLdl9HK}f@c|%v+qxk@a783zzUyH z1~WkVo*xNmqS=@r|bXUrv5w zD0OwFMDc6u>twIVi9It=Zes@NRN{GwWZopGsdW!rpQ(Anubr|;n4t@yF5j7bAcuAfqkxbKN~edxHa%s|13gdgyDES+Hf5x~Y_3!3vi!Up)Ui5ZX+E zri;cKuKTnM;70FIxy@Nvh*KQT{Ht*-(;5voqiBBtO1n4y0|FUD=QmE8sA$Gc1VVyu zg&tnHb;Ojz7;~9bs|o&rg*xn+h1}@C1g{jH3rtqL|4$A5w~Ce?9S{6a=XRo_xZSz>2`Vwa!45eqeOt3Pw(I z|JjrM?IqtE9VG5*M-)J}SlYtMSSEg)-|Z+OkbY*5_~Da6=(v$Ht;w!fcl%PfL)3hG zqrFB+Qqb0^MD~rwUE?EMju~yh=nOKiZQL`i2wPmhlP$BT6XxWP!`Rxcjj>SrLiD6I zd4Abbc%6Narjxu83o(|1S4mQWmmbgnS>>V3c_^G)*K33`wlMIm{WRbCac=OewFpUD zo{&cGKh)0uP9Mh6!A<@-OH#7C7IFzx?!3+pUG8UXQ%YZ(5j9!_xFUgTvO6Drvws~W zp4FZ|uh2BS((t49hCQi1Q?lHLciHuwSEwX*P0f7z)kno%-o!_FvI9}JbAwsAH$4Io zm*-mFh#Ib#<_Duf@Q^Xx;>fpggc)zm;5|7DHVjeffPIOuj1dtxK}d8g6yfP>`dsL} zYg!d@+1EoB6K>=1fS$2c4frp)vaR_4=-RHgmtx@9Qa8ej15#d%@;DYeQYwTIZN_bJ zz)a2|ROey(hqwSk>wgI4oP2HNCR^)>+>R!?XO{38+iZ-cZ4 zS%y`8SG?3>4?{YyndjT8V@_hl`ja9dIF|B{{E_(Sy|Ys}tT&LPM^U+epygT>+3q$4 zJo`5_aJCTYnwE-NIUIYD-En>gv9F>Ijk(X!jm!QG`OmL5Hap5U045s0_djEf+=3Dy z4-Yi~nPY@G%vwW^ERe!yV*a$Lw?UdHq6M0`?c~_)w%9^xRG)Z;N@&$d-W`2R60`80 zohJ%|MJC6OI-Sfm}(@gJYPkw89ZkkV^^+tujS!YtSxh)syE~|^8%%(fJqZY7l zzDA|W+8tABmUqlHcsIc~sA*a5zdPsp`+gq-q*LM{&rR}is9=N$5Q(6R=%xIXNaMKS z;tyWSrx*7=K47-qBxR!|I-wDMKVVTJWIs>(Ilh8)99r{$PjRAvLqf0`bK?soeJ8aK zrkx*`Kjz%N$sa!yk`dCRoO^?2~C{2n{AJ_~G9ngb=>ev(GzHYwMy3Hkfjk>P-By#*UX zhJ@FI%Vh!?r%rZ=ayuc6&)IWcg~<|fUBd585-uQ$ivgBm_bNqFxqFAybW+a2vN?Xe zvu0n{e2?o9Ln?|xB(}QUbJl1On4e8$&Po@HRG<7zzC!7Z=XBUtA=?J2Tz>q9H@)E{gWA5mE z0_a{CsEx|dP0sh~<2NOEwT|O#6LF2p+Gq$NTI{xE6&=;7_1U*X1*}(PPuQz7lmIRD z8nnb~KP4~0_6WFM1QSHl|246d5(s|JM?Y34b8s8pDx=~cwYEYw^B*mM0{Zvgw>Q-$ zX4_wuOPL2=%vZfg8{;RGko>~*NWxvs>sMKyat-!;r|Q3>(~ekQ%OPtxRnUQx6pvB} z>W?m7*KIw{ZJLHto0<3OwgCa#|E4oXv|hihu@vPGm2|9_1x)KVGj17XIQSH@MQ*Uv zfUx^OK?eA(gE~}kOHd-#3@=|Nnw$q5uCKagrBnkYec9SBIE*}_VBWw)z5Ii&&c6zt z6foK?SV1go=pxM3yjz=FM!86U*)`bv&aS3rXk!e15J!SJO_gC-ly+?Rr+mc9sFPD9 z*9I{QTB)j{EFEkCfoWL)+sxVABl21Z2f$QgbnjrqdFg=^X#Lk+rEmPF++Jb))2tg! z90Ld#9P(G`>7}2^6`@w?-<|zs{6YAE4rovjK95w zgC;4kOV5Q;4%7dtyLjK)vGV>_;@;!{&$29tRw_wkNiZ;p^`Xay&ctW%uKK0t9js<# zr11dH{6tuZWNg6RWva1nwAOWbiHm&TugE`E6bjA~C|Q;~r2DrefLO_9K13JZKoF*ho+Mcou9Tkf`zS@cH7}7@7Gz~V8XQpD0%Dj>RR_SRMR9E1{n$6~23Duj+hr@7J~XbVc?iLY8KS@)8Z8a{^OC z{)m+HWB?bGO3GC@fgE*i#MuqUs$?ZU+j(UGMn)%mLs*f+>2KZu=E?f`7SpN^92XtvF3}7X9 z1K|@n*I1Xzof(VCQlY*-0T7&~@}lIGg#}76=o{IT1Xx3Kbhqbw`#zZBaWX)_REK$U7N85XpavGVy&bBtN)ch(P7C zDF(FNI_3s3hSv;o?fIBrHaksrhl(C|6Qo$XQ=B=swP#-VO86DeLGTqnjN6|TD(YmN zF1lC%H!Nraf}^c{KG=cek4wD0b*A+B5pm4G=@8D8+{JA~Q;q`bfN(h?M#De_0+Ig; zbTt(q1Uy>^p8$SJFO?K3@YF>}gt!{MclpF2)z=+bJ8#57iuF*HVaP^;p|dXql#V?K zTF7HNueLVm@*ud_03HznHd$+;`Oe6LNT0#kb{KFNhgA2*+)Lk~ueYGID%fmTNQV6M z6PB$w|9cz$Oj`W=to(F)Ch0=!%kXGdeK68T38swI&FzNill|!peyvx1l*^tO>mc(# zP-$#H)uDNqq&4s1US3~(fbUe0|fEFR%wD^`SDaq53=)M53 zU5buy$M_>S=V$_T82BiZu!X0m|Bs*v_{6rRDBOZk;(m>O^wa!lxNWrX=^4=75byaI z;Oz$ik}e*5ejDkf?gXI`B3^qmbvc}%3K>vZDfawDWZ6r!hFP%X0w^aB;F0j(bpdW0 z!E|>t-*=Ya$V?vOfb2aOTK1oQCHVhvINr!*8BLMJ%#(-Ku341jMFKs)5QRO!@RN-+_;>sRFzE}{CX=4Hb>K(mjgx{wD6EgweQ59^c zp3|EF@TP}fG|z7c=b{HsfXH|7RGZKq=%4P1B%Bd~Ha`aT=gA_mWHBeP<*CB{m>{~G zlg0ZzH#7HB{ya!th^sIvcl+Zu87AJ^nX4(ev#Eru>~8C6-bqa_>p-nGKw?z{^6q$Yk6LD?{Ua8&t<3@S^n$*Z{W&__(iY7GH zr0SR$zBQQ@eBU{|SgCnFJPM!$*Bt}Kb2>qb!p!SLrJ(e-7UQpBuQztnS5U0&@@@FD z;;}rpm8(qg{G3gH4sq-3yUnA@4ju41C|;W@)jGw$lY7Ac+Byr%gC~k9z|Wwph!H0VQ?Bx&|u@+sipc{QrAG_5TqH z_X|xSf2N5MXS^p{iiOreTpEAco|A%35|QHsXS*m4mV&c=d6|Eu8zVUh zjP;y+D`ZYp?fgwp2fdW;ca$OFyqPQY9X4+YkKHa|FHI{s zO^F)wBq#RXH=Ly9CK4^-MK<{r94F;hr zft9nXcNveG!%&_^gs-r_u6UOkXE7&9EjsL}7M!yX_*z#3o9S8o8;Tj|eRk8m;4a3+ z2xk+FkFE6+e(O94k?e2;?6y*FG-T0Fgmm86YdSgCEoEOst%6b?StrRJ7*aFRS7Ltp ziog%vIaaqP(@m%QqO@punMXWG%^Lu}nv-hb0w-{eivBW4FXZrGV?ud+`B@k8ohbSG zYvM)n@!+qmsq5z8jOA*SfFA(C0u+1z&FDY5iI_8Ds{^XAc)}Y#wM|>vki4S~q4jwm z8=I>Dzctrys7%-;5?`4;al2V40D<>oeJpWwI&O^Q4S>=T-+us_^GcRJ#wddNp70XR z!*Lt*jHJ)^+%~;GXLJQCxPT+DTNJla?U~fp4~ai*!yNk-9^q=_u zzwb4`9L*=81mRhV5EpPF!#|H`mA`DhW-uieRp7Eo<46yzc=j=7el{&cJZmH%%r!0s z9!?tXH3!w#0JlYE7A~&^v2KeRW{pX#>Q+)xOaU?pXQ|R?>iJR)xN-xOLF#!S&9>|1 zSgp`;sr`&bz+5M$aa9Ide@X4pR=1JF;?lKu_j{!q`lek*&9bi9ml zkGbulJv|N2?RratCvyYV_okKV`kTPy!iM)Na-=_#w)`BGp#~*Vo$YJ$?a(HPPHIny zVxJAkL;oAew%foLw{dW6(cu0zTE4M%s%Gi3^YJ^g5s)mZ~#Z*$RB2n)s!I(O4!F>+BILO52>rr6$dsAuP{`=V`^DXMRI-OQmlk27qMHEI;j57m%sA zLs!A!H#>09j>@Y;!*8di7uDwYy<8slO^lpv7? z{(UcqiojEc&%>dg$YUhC^K0XMPrgM^X=gzhNeEVq4t7nn|CCB5-#6^!(nHVM27vnw zlpJpp!I2-bF=q5d#VGM3DT7QxQ6j8qS7ECG`^9>L@k^FWFVIQxdpxdBZBGOZkH;kM zMLPk`&DibVc7DyxRw$h0=`}lQJv>$nHHA=wh6G2QW;%%ABy@#k5AvkuHs=HP2Kjs7 z($0l{V~>axLCq}`E#0IL>!aswtbO)zCstSEdg+tPm(lHFrgCz3dnsq__a0!Ge-MJe zydV;U)$%v*31O-b`7GK0J~p6M+eG55@&SIPFcYAvORY$Ae~3(I8dUy_Na=w1=md@? z>qda15)#)@5>}{yLE@h?%oEB2uK^?L@*5{!(wbNiRAMAWq|?u`yY$F$AlSBESOp{M zBU)zINa>jnfDj2cKD{%&RJ?lp?QU~>510rI_&jmAx6M0k*YCOAdcQx(a_sc3WG>)- z*^c~&pI6AJ`>BXSo1u^K+lJUdi62|*y>g)Qi0JPlt_~2*Y}#Vrq@gSlMjrtBZe6MON{YOTLp?@*BRjmX4@ zawQU}|AR9BbKCcosJ?2L9Fovhi6a-X^DymlCg$H&*qft%GP-v zNZZ5LSNc+U5JWNzK0Kq-Is9^JYa^T$A3Cz4A~8_m8_@2B)W zxj>N}_z&Y9$v-A*isly}4zM4h&k&NcjM-|`8xh9wVW5zSVyNw*wRqpuxTD^je z$D4y0Yi>eBrwi53Rdb=PQBeB zp-XN&1MlYCHP&V=mkldqUhe&m1~jL#OO*wHdyKzwFDziB!#hHckg9(eTH9>MLJ^n_ z&mLm^iyfP0xhM9$Tp zrWgQ?3_WFE>RIAEYYK1S#Eb*kEExFwi()adC*i&cCcSt85-iqgzkr!PR;_Qyyu3Mw zUsNXDZlNuMw9d0RFFOwj`Y8+p*~)Ot z*PDcs^EcCf?4AC{jkxaG`2vE|(azWOQsm|SCAR-rBssIYrqgg-_0%Nr!gEyNM=PHP zBu=y+kk_>}V?U*4ZC=QhKARJtW^&^SJQyJFYN~6bAqemQ+Nj@pt#HR#!VxEGr?BbV zZYpmR564*_AD;XZn;yoC9)OnEM|Cc}#DBQbQBv8RaHC#Wc)G1eLF>;RGQ`C%ji=lR z^Q`+V7Ab${-@(d1qSj3he5uv!YS#VDIx9}OCzlHY3czmf=o2|pJ@@0a9_W7AvHnpL z4J~nQl!_LDXKC~zU;wFyNmpe-mJ(LIA)&S=rBC#Ue+wB@QlivoKtnlU-w8pY=i9&J zru%av91s?}NPECNSMJYI1G8TIX_6<&iOy%bO^jsdXFvH^_2#OHq~?I^_AtozTMPQ4 zkDXyE`X)H77&#siZ;#7Ms`SbHp29g`z%u@e&!?Y3y9d$4}l@d1o5&2uZ zqwPw~ZeZIgf+s8*0r>4v`)y1+jh;s+81;=@Wt#1!$m`*P_@l8SJBHRTugCKoYhCT` zS8m7p1e{k%Dg2LO*TmhPJM>ZJyq;-M?K?!a2CNm8WEyml7BL!olCmlx!a*MxBm*g! z8kfWRijzH~zullVky{3&^t1H6;=f6MMjv4$KXqA7;yJ8(cvO(E^RnID%pO%--*c(q z(FF6rDmi7JBNM@%+eZEnXQVbC$q(D}=D(T@hb`5qLl-K?NO0kQyH6?I_{H{=;X@?N z=%W21vX%d#$J?3jbF?%T+_|!u2$L@6iX)hox9QRK6Z`0}OH0aCeDa=oS`E+9slNK5 z?KEQ9&>1JQPuW)Pp9OgT-rCg+;>995kzbit{V8^P8hH26$HFdspx5Y5Uh~rH=zeXx z%5H5zcomx{>kZz-s<-4Uh2=fpBju6%JvC29MVOeRG0iWERFumhMvv&U288r$jCo#& zWtW{$T)G>xs3Nf(&PSVjI`8K?tlvPYCN?hJ+aQ(`8zucX)JXGjFUYTfHo{7+ zb3}HA1DNczjMos%F1IXvg=)F@O(7=euRRi7u5cB2F*)8u8^)6lgOx^Mk(YvT5p!!2 zLuG|+NoWOd19NIDb-RY|XXjY)blImRTfR8g}KP~jTLJ@C@&>zz_jVqOW7 zdn|kMZE%RLS^Ji?I>r0mbI;!pxU-QSA-v2+ctCeietwzlf;p#L%;T}OAV&nyC7=BK zg~`-BRXd+Nr&+xTXCI%b_>b61=BQasu#IC|hI=knCR@6tZz(C;exGbzU{g5c{!x|3&Ew za{R*JkuHVqC%ODv;D4nYn)Lc2+|R{cUHGvb8kxG@lM%45g^vur-d{ieNgn)>bxPHX7{DU zSOUhN@z-SbQ)0OUkhn|=x`YqNh;U!Q^U7Wyc1WMIB*O*tTBo~3(z5xiJv3kVRhvuy z!|E3763{Xx_pQ3}iDS^>Zjnih)J|JrPGs1bAb!3xVfNKMVSXV`4yV2WnPm>Iuik7Q=LvdCN zku4ZODLB!l(oCRG#E^a5#?&1toYy~iAa7Q4GfYae8Rs#h6LwU2)U%}8rF}V@ebE?P zWB?^j|6ID^K6w}&Mj}9rQn-xzK6S6|;<*R{mHz7Mq)~L7xUg)Hfw@2D_3~JOFsxTq zC*oku0DRqf+NGuTJtdNTIco2P1=Y#CHFlB!H@2R{am{V@X?q$>sFI_vgbmh*{>?*? zMjEOpEtJ33Il!OvByVu~_E9;@l4Ug_)pu3uxI{(wq4%CkY$GVl zb0^r0Eb8vumHOjExQ_r)KVD3*3I&qReueruG3w>i!nq_;s=;s;M_Ndaz z_H_qN9M;~NMHgH^V^u$`_166S>_R>1pLDrO%4y~~rb~xv>7J|-*dC?j>h-Pm4sBd= zG4M5BmiG0~?#pi?NHHZJ`*%wmC4TW}P;WomSwbHhGD3m}WR65naWr{tRr7R8>cnA{ z@JGbw!al;K?twSgN&TBl3TXC^wEUD$br2rLnW1~1KRn;p7`W7lXjw!^jjR(=-!C(^ z>+_57uoiq-zMbVsaKMWQ-Mf*8uEFi~Dk zRUZ%Vtkwi6%2BRz;G?O<|B5zXI0u1=I_E3MBfJzk#g+?rES2Pl%$4EjLB2id_yBGc zF4Y1UX4qxl(&NQw$jz-daguAW7kwf!?ciT4?9lLjB#xkP!wZ->n7YsSoS+;kD&`el zr7xhLR`l>-u%#zDW8M6gnbYBL@$CWo0|^ye35sQ_ojkhwuGPdXi&0NT7^SNPxS-9a z7pv$AD$Q+kzp{OB(W`0~rXo9%qJZr=jnCQl9kS9HQAsJYyzTfgUE)3({{=ZpE44C(lljvL*yiC8&kd?uFTsCw`fZ`Jb-f|w~VQA!V?oF zt?ztBm{bB4)|R$XkQ|dnbM_5V+x?RIuGqP-E2^+hX5UO(Bvscp%prcTGG!KBy!Rxl zdcPW4^D{)AT5v+btN?CFWd>B2!N*Wn{|S=yA1wfZ%<|@*T-W+7I~-h`0|cEeo%wWw zj!_t7Ey61FBt2um(OctPBsx;{`az*8(IzmjMd}7JNsRt$_0z_q6qXQ%krwUu z4>A7yeeDN7MVnc+c6MR9*}KlAk+i9DBVFQP{yW433HmER>$kzUyR?Vf2%N(U!$0@? zD&7!w)mS;dRmr2vZhuDXX*=>~!0ge|K{~9^<(GyXr9Ls~? z#y$GqP06*AeD>Ua0ol6)UADUpI(@d?SpU=s4%LrX9iwW zQewEZfBaxcLHrU)J8czJSrd&f*L-NHNb3nU9Yc*G6gP<%yIR%&_Hyr1&@86JN6)Wj z+`Pn}`}^YFYYiP=>>xa^&q;sb7b;6+8v+Dwy-2o|+uMepnvspV> z|FT!*W!o`3KWTXd+h(A{(K z9SUIvhc!b4r8^nEeFyH>^V9L`a3(S{rOqAHV#e$@m~{lMZ*S!ZeOsJs&L4Ifp=UUw-DD&9$2;EP6D9wdvPMgHRWk!(h0j|?u#f5<|Hj6^~oL!cG5z#y$279rEce_BPuy$B+qZQm=H-_ z`L7#;jRUIN1HbO;r?#H-l8PZaalr)e*pD<1+JofEEA56-)R&IgE^3qucGhq)2P9ic z?XVau8m=g*d)2a3&QDVlgDLF|_QM=~W59ms+KMsjg|{qVB<-S6O+!d&rXTRw$6wEW z!>nt_aGL&_?Ku7sknc%42x5y|ai_fVfER2ZhbDW{kXv5J2^_LaR$XIp@09zC3X`Zx z4s?Izg%NC5)kpC8nhRg2C3#J?UP_%%ohwtOGzHaA9>dPl}(%rMPW}A ztXLQCSWid^T=wL&YTvsJDt_?3>3^!}=J7Fk_sU7s>pf6V)F2K^l)R`S#5WmNo6B*$ zR8I~l=z+*L{TBW%;#FD3HCw3}Eb`on6R=XD{XE^)fxg(FOn^l2ivadtVG1w|Fpy+i z|J3Z6-8^LkS$sE_$%tx6-HMdDj2EMf1nNe~=m#f&J6Ek$(Kr1zOd^28$uf@?m}1BEr?A>K=4k7H?u2kuv*+ zY-Z+1=Z?ABuON~yU*ayutdS2lSG*bKZQgZ$e7<7=(_(adOzGje+mVWw%^XB z7sF4yiIQg@y+u-%wz(Y6*c4<*n}a9lwwK-FE-r!&JgZQ0j?u%~JILT2uKm|v7#@Pm z61w0oo8Z#B6O`%l2TaF7{pg}V6@O}G*a=dvoul#afZm9Kl~SXXR^kn>esb67xdSOA z5`;Q z0{2kES?9G096!jc6sHU;NYTf(&uy<9+>k)k6l~2mV;MXFwCn^ z7-_6gM6%BsZAjTr_3Jt%tp$W@tVLy{)H1^fP3jpCx8Q_VELk&V-9|Ep^h#=Fe#j>y z)i82TCgXueETjEET@5yT%x)t|;9GC6hv9%4kiNbsRv8T8p_J>%ER;ONNbxef#t zJ?_1~&3pdr^Lg~H7qXRoyGtofNxbet#5&;)O6TDzdU}$TiXnA*rzd4%$T=Zs{*#*_Bc+?Ye8%sZv{mQeLga{UDSR4lKBy2KyOq=dqt#WwilYT4&n>=Yxv$_-3mx_hv zwS0F$$)MPu^YJU{X7nfn@PhYs5yz9?fJiCUUL`d<1`6W} z@}<#kFZPOnbw5xu^RS3X&zy2}tEx?xU>QGrnc)djZ?OAg426sbvjRpevS85lz7R9byA`ca zGU0E!JcqXA57UeD9zS_v3!RusuQ+_wpn5LYFM4qOmV4QTPw+5xxH4y!Z-;lt>~1b9 zpXEELQ&0U2H3Tq<52iP#1v&kYu`fJ zx!5tieW3&)^O{d?{KmrW*!6PC6Y+X%yMc>`oITg4h(^Rx&pXAU#3G>6~{>m`gu zC14$9XL5~JWIl8}z>@IV*=chWOygZOo!a%&q`J)6;f=Y$U<|8+=sK=EMBWvw1$-|~ z9!`awgl)VWxr9w)qdG>qybc61cyQ3aGWewbj`P`%&)Myw`1LHJH;Xa)il=TPYQXhG zQ7ry>)NQ;Suo97KcA9rENBA9DyG3R-Wr}J^;+l;=mWDn&maiWlKGvPmJ0|tNcu7pb{j z4r7H(;9gPqf&{<3ndK%}b`mP-cv_Xza(VDIlm+eOMR!(%_#N0k24Hr)_bF|>36yCqdHQSm!2*_9Q}-C)4D_{*4Q{~ywOK2$A8UR`9kw? zfP0$#xhO-AWNEkLHPH)!Jt?{jcd?#dNFL>DK+zmwPbqu->z0apcGymK*_X}sVCfZ^ zrApposVqQPK2>}@7>sw{G9_jrBURY#_?E%z$-Gfi||tCJ3JogFF=!p#@=$wx$2+g8=n!dJZAm{ zCj3BfO4&l+9q_(|KS<<@d5CR{XKvx=7&e-mjv%StJ099) zi&@}^{^2br)NUHO3GMe*xsKR>)6bpn6jL*|Dlv20Ghu<_dGo`v)Tg(n4cG_pmQNfAw)gVt#hha)imJ#mKunLuK7P|mdI_vsJf~Pq1w*{f9yYNAhytlm(Sh zIy%zg;ndsK4ch+nXvh5@?w+#{-)oB%ug$in z{kADWapSeSe2IWrm$5OB`IG&+;Jc0KDwOf+#ZJub8;9KA3sXi5)E!p2Wj>NoZ~(Nq zWv(!=@|7s}TDu4GNxNUwU!_r;-wq15z{H|?l(WT#BkshH+v-gf@trn-TzHSt2>d?^ioiZ6NxULd=uMt;?^@n%K{TJ_I+bJ3k`4$T?+UWC) z%Vr@Uw;UEzA6ZC=i_wq$fjwbPhVHqo-+vwHp$YjDmu=bxxjsB{BAFS89=wOz zUel;@N0Par=Z0p4gdM#Aitnb1-Op%c^g?gXdtY15-w=XI>8Xyt0q@9nk%_PSI-L18 zva{r3;*>9@s3p5s!vZWAdTKm_Hy{{0rlzY5_E>elR4jml+jo;J_v*ag)}LcU`#V<0 z`OTt)0a!ou!BJDz1nc^=)*wAJ@A&2=v9zl4>o4d0Z>l$*h;cn=cGv6m$CtcM9p+gs+LNp+ z{k)JC^F(J(Mc+_TMDH2lV!yt@k-8D9*=$M2oUXV>vYocQwsyGaYqkcWwaG8=t%wZOHrd4?x++&dK?G=&Vq-pHr`393#V^3gBzpvDZKh*NOIlQHP7doM1u_S|JZxWs3`lcZCnNr1Sv`B z5b2Wckq`uF6r@Ax?jBMQkO65F7(hxuy1NkshVJfehUPuF?*G01&vkP>YrP-e5BFMp zK%DsH-p4-nvG*w;u96{>);TkT$Mu2J=^^r_#&z)3)-BGdcjgRx1KOTuwi&jjCK-x4 zKKvOPDNZUnqYI45N}2%fA-Z@CiWGKC=m%+C@I1vX+gk%qBlpHjIwiDI3Zfo#Dg~`) zsXAzeRlqhYrN4Uio8t`$GmHNT?w&*LjWtr4z>DJ|Nv!;h3r`WoC!#{-nZPbnVuY0^ z*ZcGj`DL-DzfZb1eQ+3HZ17Oab}Nr0-5MKYt8JL&QnVjz?aus}&fq*!&G|^HTH7cT zfcG9VMX+gOeTS4b)3NT@f+t^hZ>(lGD2VQSvj>;o9)z)5LyVO-cJVfK@(MIUNntii ztN3j$j}b;svA$uob+BwYhBfNFy|(ZKzAOm;K@efxhd7;}hq=4LRzk7M4vDaNP>*8S zcl}m*gO-MX+{KH_Lhc|M5k~S6$v!i=q4gnE!tX_~YiWm_)j8Tn6x$>1p^GDb zeFk9&HczE*mT&4hIGO>bU_k^Y80HXy>wuD6(X?{4!D`BlJPf4lb9?Tr?sW6g29&DiyaE*(oWB_#NkdG1L}KgAdl zTqTChr(9+_Mcj}J)l+6|ztMC=oT(|urphC+{mAZL>jPHcGj^HH8HU`0XS;2mxe3tC z7QeiGYHBQzC4yv~1w>a$_SZ&`50A{|#J>P8?o0-_M)B2kzF%I8T zMJv{E-=vRJu#0~Fc`E`6^I7*KoaFGb9{tmC9Ihba=DOEK+nQNtZ&}aph)?azDj&%= zPhQwwzWn4{@gOsGIWQC>oCOeIysJR6H2N#!>UY z`2e5~yz9%Cb^?)*9*Ufsh3r$c{itk>lmb;|3*UGecA`Yb)WCByoT{C^(?ht5z>o1%E7Vg>|93(o+l6O90s1y=Y{G zm)BYxa!3kW+n{Ha!=IUSBB!1}GC79bk0zch*eC0Ge%9engiyDRkv;1PFfq}H8;(EXcIF$A9_ z)6Dt3tKX(;_fq(LNMUA_B&olu65;#q^gIBlz0%3MNaLra^#nA7)lZ|mp}x)e4Y*tR zg%T@Ng)o@+2H9JaJY+Vq4w|BEPqbk>CgD`NR}vA_Vv$Bu=;mL?)97 zRinP63coW@75aq@`*`6^-WzJJ_OMtV7jb4t(p(_pAuI#W^9+>}n)EoCYns>zNmPcQ zkFbC=_=A zMR);cS;I)c*Mr2t!N{Xk+CaqC)Pfg=i}0as1Mt_0k<3#Oiq? zxzdlRQn|o@{>7OSDn@3mM&!2rs|WBySi*74lyO}WDagGDf{6D+jfe)c$$re@z39Zy z%fb`<3l7Gi3tU05{|wuyk)G_!`C{`FhG-FnkO(|@@3=-?r%zSLAhboMe0)Y8(|D3H zR>BGwMPF}ei9A2HYEuw+JK#$(!aMFg;+&&jEH~gjQxEvu8E(+sZiRu8B3ddW47EKB z=?}sIkfQj>_q01UNB6wisIQ|fW&I??6^U((4aSr#Q&0I6cxrD>fauyPAvvEWCLeA7 zS~L7)T5v z?|`|b+Ub^R5V!+tobBF6Ybc&?@is@F%6Vxva?< z^;Ia@rk>0Olg1Gx zG>@-`g&NRjz0RMzV)+?g7k0r0X_-)-VaGJK!f6W^BLjfM(PoV*%8>lR&&FBs`BIqL+ z>`Kptq--{KQ_LvNfMh4lZVnSCJ&R|?s}2KgLcE}o$=e2r-V!B)-@4e@nR@TU6@J&_ z3ZEJ!1b>myoO}SbRh}$MKTOB$O=?6U6z`Lqxu61c1Kpe1fG^%U-2)=>jUv%zE$EeL zOs3s%BCR1MA2!okpFa&>`<7-B3eSz-pv5dBKzUIR0r&Sme%2qNjGj>b8TO}C#q3BG zek5vBjh7Y9BRv2wGNCxM`5w_xCvy04vjba=o%Aby)IrnZwq!m<6))c1Zs~>aVCC9n z{f_uxAb3|$(KOF0@&F}Op(Pb!sPc<**k3t4Q>fEcfxk!CyzC8CQaPWZ6(F+^u@PgK z^Wy`Z9*Ptmk)#Md{)}%UdECtK(lk_g*fujL8S8D%#TJ|T^#O*}N256}H5aE2P9POb z`?W(!#_iNDb7nucu0H(%Vc1&wX=5Qx*0%==mlu}rU2-tS_C}(4YrG^MKG!J**$0B_g)DD)J5dqCp2baY|Ja!ns^m_Fy%GAj^@JC4TWw ziL-XzMG|EX_beDm;b27`p8=2ySl9FAe(N!4=m{=1@~CGzLQ2-NhYS8hqI{aM22%dj9*9Ls3_G0Txq%n-$20Jh5T zIuIF`uV~#ULVMO=fD!Dzo+~SMNt5to1yep04ZesXql>!9e;e_0PVd$ADg91FYs@Is zgzL@vh+iDy&t2eQ<(h&ORSYOoUKn8~hIHK+8*+xFSCkmf$#4TO22-p%Mk~5EsdETo znD`=+RpDON2Mi*8MlA&Y5ulw?K7II@X-p@GvT_zZ`S&x(C@Rw1IWK5>C(yS?WBjMs z;YyrtO2zgIsc%sS>(?RTM?B{T51aI~KGl88&N$u646lr;%8itC>!Xovyx^5qOsiPf$e9SP!4l6f*$6fpKj3JqZ<#(ygK}okb)qiy@&^WE+P{9{N8}b9gaG)j({&r1;LM z{EjBMNk*jmB%b+;E#<9v+}%A!;O_lfL4c$(-<@zul>U%%NgLI=&tm+7pV`M9$(amd z^JMU0m+Z^RoeOnz#bEVsG|bq*<&(MM(g2%c;K(+5{gkpxx0x~e{rNdYzzsP7>SeKo zj_kOU?0Uu?U*(QeI$B@4q#cLJD3@-^gY6`M09dSaRFa@oNCbu9a4jVOu-vksc*X~z zm;LnLIeQr@-%q(n`6*2s7Ze)=*ai&lzefg$>-}v9v-_*!@F;(acN{_RW>?*d^dix1 z!F~OF9NFQ3H_4=`~4z%g90`7d-sJuC{vtd+-xX;fq zkDdlMlsHjKJZ_68>A}zdz)@}qS`gx@357~%?S%FlPLzWxN<>SNuz}D$=f)V1`@%}< zSa3L-C$WU|usAZK24zHkHh@cvVs*guWj4P@e(qsB@j@9NwTLk_EgJd8Kyg-?{K}lj z=MvkL7!=Vv0fs(MJV1hfe-)m=V_J#{Ln~A@eog&b3-C?7eC0>)p4zW^Qc;@*${IV= ziKOp}1Y$gxE4KA$%^N;9=?Xv@P18a7tP^X|Rx2)Xa5$PN_069lLR}c()VdmXY=&>x zNQtzpN?gEfGp09;FeVI|UV?W(0>tA>LQAnU_J^QQL4I8jtp|mF%n_yJht$@ZXAhFG zMpSDlb(M7oOIKe>)AZK3Vp?eUd`=qCZ>e$JLbA{1Cc%y3#^B0epz~PI<)k-ttzOCx zD|WZiE7De}UDYON^zOM)Pt@tOE|hVCp*o_J+TNeB1)k=VB^-(Aajmgjb6Z%SWCt<= zO#ED51b>Jx^jGOuQ(`dg_vJ427$o^LGub!b)g>Id(`9@DT?|r3*Npkor#ZR}Uk}hJ zKCJ?&Nok}XQ7xN~%z$rW{Xpvhl^6`%2%$`84YqC)!j&NW+@iD*4}C3>)Mp@Z92p(S zv%nW6MnTzZ{d?xTubRUo^MyHL> zGhGk77nDi42@(VOg5!uqmeBsCN6)B(ef!L$`qJG)1c3tA_HW$+KP`*404BUBWf2dR zM>;Jd@otdm$X|%ceBZlD5yw`UYW!JPqxG0C=6r7Q5TcBsIBa_^FDLml&L`61+F8rO zu#hpZ&&;2T3)AREpEqLit$v!}m;=e5-yxJ9B{A=c^APW%xos>DT!yXB9+d;_AwRCY zK*#oMT6~xH`zKmm6G4Q7QP{_SNJ}b!fozla&6*307h2~W%FO1S>ajB-TYoOJPDi_} zRF+V25Y?%2+98Zf9%0klkE&36Ffg*j{k)BC>E>xj>JQFjHB-&q z=jU5V^t_T_Aa<`lIqCmWl%XDRH7gB%m|Apv6GGvQZS*P?RZVUW_E=GS!$$r3{Nc|V zcj72-9LdR;^OwmOFK}@Y-Xi$1jSZk?^8hY(EPI(79KeWAroMQA%Z36Fr_xA2s>ehWL&=-P($ini!+{3igJdd1A(X7+E6iZUXECP1app}b%A@uPz-4-Y=0 z2=P}c6r-ZmqD~S;z|VrA!c3K+dH7>$TA@bwUTkD=abW-}rl<5>|`z za0aT?D{o`~VwL!$HcpZu1cHgq*?tNxlzAsXMYK^ImOgG0q6lQqWF)B!cQ+TRCW%o5 z$Sl*KAqSJGUlDr}?Y11{5uOydkm1Y_GEY2O530>_Y+ekWZOza_suanY8mdHNGJmBp zCL#E6hJ9(1Z}Xcy`L>@%F%7fnnWgP|xypybiiR za?aJJHi4J`W<)ph+}3j9OPMBIvH;3T9gatt9m^lAD8!$BnQ}dwRss~1nEgz%_`ND` zzv#y61zp^Zo=>|lrV3c`Q<$ZRKpIYUd6BHQcz-vXMy)YDgW*6WqzY9+#BVI5un5{2 z_Kct{&xP1`ozm_1OhPs=w?=ORJN&V5w@2TtDEt;&cb?OSyFY=WAO(f72NXObzUU%J z2!_fG!y4YsVb*%oubLid8tC10$P{F0@Gx>_^?b2*#iWI;tOWFw_j+FZV zc45$TdY1X)58lQHK{z6xHuZhbzF(?ISR^zOXp&OuU;X&pm0*t+&eYpKYI&>WKQlIm zA@eqqB{LmoiT^nR&C;we&1m6r(dr7_?XOFfOT^dJVRNxEs?e=eCh}ORP?zqX0Hn0X zG~11_v-xy2?V+9j;3$ODjdc-P{dg4*A2viL#OpwY$(Odd=c;_YcWE~qM!ZkN#Hjtp zRjSYKgVOC}hVN<_pFw{#KD^3Q7oTb%$b{0KbP<|D`&m)Qdu{jQa|}q_^xMG%i!rY? z(&z^y_=Az6*JL3Qu(DQHKlO)*sA#c6x8(KGN1}gD_c)nQ}91^WRYMq+;tml9xiL+{`WW%rjfUQ z6bSlH*)#*wvsW*8bE705X7o=Lg5UghZK6SE^gdA_5U+g@v1!pbx$s+820X1+*tx@B zNv5A!*!ZR8lIkSS@l%!NvItNYAOn4?4rLtNtv)3hF4QetuT$oD14xVdVYb&A3g8Un z-pWT9!|$`&-n3EL0}oRXpfb$$2EAoN`G$J(?H1gwgsIZFqV)dc)dw7zIDVvF)v!82 z{DFkA2KsT5G32}GWjX;!IRbLZRZe>UNUP_Ij4BuFB$#^R`U_Gi?^%MYEGCPxdhAcBf zhN1xdN^_KjQx~d3PsNN6*TLE}klX&eo|a8*GW;}T+cBr#ak*?5h!AZvdA^LM7vK)@ z9j=pu(CRfO1qn;v{bw9}=hJCGmL+I4D48y5JQZi&A&}G;Ck{VmJm`+2qDo9rUL~a3a9#oGQ%N;vW?e7 zQdE2xKsNoI)rw+0-rKp`agQ0M@A}57v>vk%oW*_-g?Ha}CB9VGs&Fp79KJe->hJLP zX4lSPTo#U=w^pMirJup&41uJ01cAv-gJ7&)4?n(8R9F>9QkA&Y;_ETGl0CDDmS_WO z@Hr6GSp2>$ie0@Jww(Y?e&K(`8Y)Spos6DkA@x z(SvWQmuqya_2MsV-R5e2M_&YGa2)*{YZb8~f1+4L8g<>-t=PO_l`q+Q*solFEIDjT zXluZxigPiDld{L#m)N56JJk67{nadZvX0F}j^8hgnFtksyiQoxw&Rq(d`^6=IK|kZ zFc=dCKT7i?42~Qc3<-sbN&h|yGnmr;apsi*D0|+Ri$;31@;uY^YAC4!8L%*S{9;lw zoTwWq#NN!v7_b}=Nq~}O1)e_bODir;!;%6x&?kWGM+h|#n>DMp^Y2_f|&2Nqy#%f~34 zjs-G{`m4T)fZWjhI?6rcF;XuBn${%+o^tETUh6OYb1tfjeY+2$+++vYPz@&)2lcncW3j*!6ei>vClQsm z$=)~nqLajr2o=2enet0~U$KN~u*lcG_J2E{g-l!WiN<67H7CHz0sdBi%{gc8f_Rhf z3PXNZZo`rKf+~s|G%?h8Sml~n`W8cGWob|NQk(?e!$YTom!(l^A)RINwcK`feNh=w zSKWc=9(EayY5L(qh0q~BB5__~`Y=6Ia_rmGiw!5) zdJBxNh2KGH9!dIXW)n4l>aXBx2FIWG>(H(4YFtAazAnA%de-3%_=!SgtchqKuH;ivu!#j# zTg$2W*oeQiinklsdf-hmUnXIPfs|U=rPmrIdTj|LMJ2IL{|F+-iEepWh+jt7;!Pkp zp_rxHQ$VC}&f0zw90q8Cgy2AMj*7cYk+pc@?c)q5!?F`1hre78ya-^VoO+G4D_q(4 z6iAWKc;bBMyuT(OS+E1ld|$S1p8SL8K}boIU@~J$Ev~duznV)*_#luAOJ9@ZHWX8R zUsHejlL%4FoOnyi+<5iSC`pDgS*9uBd*CuqgEok|1_m_P(4SO3U!_Ka3-3V|D^%oyPmQSLdIw8!OLx`t$uOdml`44 zu7RB+8mBu$jMs}??e-II0o1v@kKqzbPGQ#UongFpKL{D7nSTPGp2c(hmUb<8Qj`1j z=#42)cQ7ORSC*XP6IUH5kIwBR1m#n(Vxi37$6LwGGL|csjL#H~qoZk88U4yP2d22X z3VyqVjVJ9&9uuUhpjC9L8<&f^e2I=3Jg)SJjkP9jJ{NPP--34OJFAQ{<1vS~_BuBpwg(_+1U0|ObqRvR#yaPAamVxSli%V5+ZwTF$ikZzvUx#{g{4agxhUe4!g?AWSPJ;t*PYEVQS8E>gqRsS z-u2o-()T}`!mHdZvkN%}=*Ny0Jn72#WdjH&~VnV+517$5&ox>65R4* zh_F4Tia~1^t1|606;k_S&TsaKiPWr#^pdHeHLkJ>fR+`=XqIjnbwHCE@E~ay&BW5Q zhn~-ndS%cicl14!7S4Kye9+DInq$5%;l28e#?1sfr(&m3X>F3elt@qAes=G$KX~Ny z-0!yJp+q(^3KGq^8Wn!PEJuH^`=5nS&d;Es24cPDS%3p#5UBOKQV9+RL%#tvtB*RF zNrBRLKp|xgDXWRSO*342HO#L2NXA_dB^#c5WWAZ_r-wmqHrkS|xC4Da@mM8_^JM#T zm70>&^lyf$HqrW7GKq;fOl&k}vFj%vZR-MRc(;UIMP7{NF$q3OETxi~&Sm&b8K9~B zW-Y<>^35=t_Q1+k%iPuzfL+Ye1K33ZxYWu_{UZX%LoFnn%nMqFSB$uKxBSrUcN`l` zG^^_7&I#7M{^fi60%fCs&b31K=J=ynKr*0n>7B+G#Jvv_#L=sO@>!ly?m^Gdl8&|G|Wiuy)OHq*8>+18O=$~v9HEM{dzxoIr8VV=cDg&9g5}* ztlY@;dI@1U3zDLv99eeLE~7!oaV-M|d~_bivTdpEHDznx6MhwQWA}_&k2`M7;HsHD z97^LR9k?tf#`T;Y2(4Mk`Pp%L#Qw6i1MXt?rE4nPY??J63&e~8aBAjsAFil?jOY#b z2drjkUyznvFPf+1{p-t%0xZOqs2KvYZ5I(2aO*e>r&+%~JmUQLw&W@j zlFRfl+l~MMW^u~4JN6cP%*6#HCXtJGawI>|oCYo#$jJrLuwySa7$n>CSj~nWBE;iP z1xw>KYLd`PPCm$nNUjEOM{&BR)1oXkIHfac9wDAIE~VxD;&`)=W7f^9rV048p^@M4 zmuN`ozhlK~xr1c~{9J;=2_lqI{65t=i?z$2QXHxZ#ChE$EMBw@tq8-rA@1+HJ!g{F zYK8L=es|e?likpNuriBq8=KHeX`45Uho&UnI$>@hcQv;!`9w+5<=8Mb-L zeS3i<1x;p+6V>cuD^SQOR>&5!0-k!Y`(~UaPkSSfa*KF~om2t-?J>NwgO*kpGpaS1 z_9Aj2gj@u7!Q10J;N@ubXdNiVd!MUY?26#DURvScSv-{A$L~gunBq%{>)fYhCgL8_ z)Y?eKoZa)rTIE`=*sJr2!cO^I!&*0Qz!93&{A=xwhOdFTB#!U01m_bc&tDD^7M}+Z z794(F&osa(6W#J&q!SKWS}P?Oql%l=_rd7Y*Yl$uuN}E^sdOXilt)W4+5P-aOGkww zAsCe5IbN@?%eZ5{vQZSf-CFUR7)&rAaUd(O_4vn(19FS!TF#jFOW%a16 zq!T?X%12YFxI+e^?0oU1>rIlj5w98%bNlSD6+(lhCek<{H`qu$^vp^F0bO=mugwd&|M{Wk_xToJQ8BUarX5+1O*#W|oBc9XGprn@8 zn3$mN8>!7P+vm{mMI4iK2?F?cfFTI7E_O4+C6PkYGJ8Yd`41j47dT6l&jjU@$T|F3 zj16SHN4Fx6rlTpca!QE^jHV`lr&_?M%Q{9(C?9hTga}%&dgzD~Mg=^SAf_D6Egqi3mc|K*N& z4{l2G=c3tq;F3jd4gt6dmkd+QA~fcc3Khj8pq9_ut?K8J3xLTs$L3THujGriC$Z8R z*nccBE*#shm(M?yrcFvXq6E2tm`j_p9}r5xY6-1aTN!j^f#JY%zs`}d^H*kWe%kn zs895~zD>IkpZkNaixdgc(oo*J;Jb?wbVpu+9ii90&+@I+NNV)onjby*zE+DKNG%SC zd{)8u!FQwvVPk#7&&Ii+(&Du82a6wXJ_^IdJgsi?ln)o>v-xQcewfROB9IF6G#p^p zS1(YS?O~kVXIK+w*2(FE&fb7%e#^n&-bAVWY`_N2tG+I;HheP5=^iv+dZ%g-QSS~% z|A^2&IxOC)lg~^ofX?qU=A|B9GSrj;JOtKnVtrV*nE8ysYnb8ntC)sM8s^3uMY;Fx z^E42j$u&aqHHJ50$n~pY8yAgtwPTfklzN zFp-xN@cVp`R>Y8TMfM*;vRojFVe_E`2(`chqr&`(kCg{YYB5k!F;!imM%)`F{x@|E3D2D-7?gXpu9C!y_HH}w$z2pT1$kc1UYymP`m0+O4 zyH=(A*jWAg2q~Yw7Te1&OZrL{H0jGlxy5nqfqm*NHbrNf%td}6qZe3}!7lZoP?F%J z^0(irQ&pd_mnB~VT78|8id7NkKVTxWDDWjV4_idpWjK?=`~xW{Wl(}~M4w-r)NwnPu`z-uEdn>EWgf zk-r?c34 z=93k!7Vi5&o-EiHMF&Kh>S}f_n97(%uPBDhB?Faq+av!XT~x9FSW##VVV?5!rGAcn z`NLBTE@`?Y@rCe5>nB%3-GhSfG>q3i0;+y1AXlcM?C=9wVd4TVXAX!2zG!6bAL0SZ z(wUyGG$N#l{Ph9r0TIWR6oQ+~hHMj`f6`N>Is8V$CACOmFBGhV_~it2c;q*pwO%S$ zxEZSHBpIpU3K~2%l1YwOZ|;2gW@>$F!75*-xrIf!^v1Nqn?J-8sG`$l5`o@JE`fo^ zcb$;K7aVaB0>WR3&+5a#jQf5AA^Ry$LvUxzGq7JC8`9y$r93n?qCJkn3k{4`{tRih z2r4Lh{6*MN<+@YfOtTU%jzZXwgDGgp#FF$C;~#y`Lv`4ge#^$vfMxSg=y$U(wJvmf zyO%20YvO460boSoj>zv^$Oj~#Ph{)~X_wDkBH)RdfU4a3LskDeeiS1daZJ%d9~y(= z=^{%)W(Wiyu%LcJlObX_*M9rqew16T*>=M+ZPYf~=dvQ^%q^;(Xg`!HN<_9j)}IM% zc0WbT7|h^!Bx0BOvXZ}(xCifW2T_t#1!|~ps%2XHe|4slt{Qf)BONVd;JNP`B*;Le z+Fo88hJ1ds;pQ^R-&Eo(ym_k~c5ykN zfepO`vV8ju^>~6XD%u~A3p$Ts^aRzf0XUJ=vMmW8d-ybuFbAkZO)N*7OT5ugYLRiJ zP=kXxP@eROVAaQFIp*iLM6eV}?lkszo0kmrr6Cs#!5=!e-?_A3a#$9)+d5;oA);0< zRQs!`I>tY&ha2h+eST13!NLJP@I=5&c-u=K7-3!f1fs+XCL^@_QKCBaYUUsNe`^7# z0f|@j@xy~`&qAz?(o4F=Jr}RG7-+}1CW+?{T2?x&ixAmBMBgg_adWnH$SU%BJH-&B zU4+tcw)mFh{)vjpOQfJLH zDI+9k(NlQ@K!WdI-gVeM7_+^4nw>UQmrWozKpq>*=yfO<({K=BS&!0uvV;rNH=DD4 zx_AxDo2>8$GTU%KX6Z%Y5E%9*Kfv7Rse2TtDM{{A<26R@=0NXH$n}6G40{ z>eC*c9U1HbX4o<|neLvaRt^R`V#tV5Z^Yrq3T^o1C4fq9Jqw#-Mq7=krx3A5M{P$2 zy*$fJSp(L1sVT_-_+pt4jcvRsX3A|I)SqG+>h zJ~I1?+-qIoWIS&gzbHY;4(dI)OOT8YMB_r1QPKMa%o_K^1>&FuPB<2$*MXZ5acRk2 z3v`#Qn+UFyT1ydtPA9jFuD>sRSl0sCpd|^fBwgC$h*w5(Of#`+i(o&a>i0SxFnN5o zt{{a)waL{~;Ep=(HZ34P$~vg_sv~vF-jcIjOuJ1v{Wy`?v#>**|S-UmotMTu7on_dWD(OXnB z+Rx3SFsobDk!L_%13{>FZo1yBoI_$`&p5Zc-`qj>4 zq8dXn_{+hR35DbJrrkaZJ+pqz1o=O>eH1FcSU|!kB1_$b((=MHeNX-cY8f|&ud}U+ zuV>RHNP+qeN*^uiK;L)D&0V9{X3ycTXL`9E5%&Fzr&q+iKN}%#}U*tFz!z@ zw4RJ#73;CN=orA+{7!QHDB5&CH6UT#;&am1qx5WpJT7vM`8;ip9%uOyEpm75{=*Xb z`!d1?Twl$-hj}YJr$XWBFx>AnY?&}kA1j4QnhNZn+nh7#8z6SLX*5}p4!JJ+ff`q2 z`*`$vAwc8Jr!$~}A=8}6q0~I z^@E4B8pU68E=6yZ(q&g|T>$Y9IF5?tK9Wbb2lWE|o&v-WzGv0#?D}k~n^Dj(-?cMb zHGxxa^M*Qt2;lf9w?uiZ?4NWrayKdoxxR2nNg0uo%G0^xcF}QB2+K3H(*df~F%QUM zpff-sc&p2Al<#UPB4Dnvq5i9qCCJDtVNn%N^h~pj!iO*YAvuz_TTIEyyc5i%*GT>9 zfREILH8|r}$%a=4)l#AP(1c5RHwBdwj7i(!AEc5n1yBmP*)E>z^5aFIsmrn>te+Ma zo2Nr#zeD4y?OB1Ik(T*U!-};!U{b##D+u{Za@jm=y)j$C zBH>Cl{u81Gs6m1_sk}s-vh1J-92g&YZ@r%-wN@Js}%kwyBl2(r|SiOh<%6rK^@qNWkQW!$Tm7UeCRvKme?YSSs^ks+MZGiE5x>$imD#CFI??$*FS6S$S;OP{(_?w6XL$-sar9>~bP)g2#;E(FA# z{x)*z8<7?uQVO%5e;CLB#Ls@_&!!v|>dt%36lL(IEXf}^5*s(Uw$T2S)(J_Gy&ogL5>%E*~fWPjac zY$t&9S1r^$x^Q+h8#tY>nJ?l0c?U*nS<9srH4x)YmyX|S#$>|!Rq=dqm}F?qt8O9; zym;D?$7GassemGn6`%H3_X9Ei??Cs6rN1;{TF*F>fGATjZ;T7*DD>I$|Jw_!yS?~d zuLNIEMy-E@YTtn;G18&=TxHjpffvfqg#m`lzm7X_9Cu69@BCw05Y5QRg!jJZp`n+Z zH_w_ZPV!bOd`zClUUHuL{=j6Ls6aiQ$L7(Y&?o5hkp^GElX9`)N*&o(XXLtnMB!J} z;(?phfH~aa*lH%|nih?%^>T<;QqV&KtoVpF_2qr-7Ow1FpOKk<@ySj6t7)x5YMeu} z+&NB4e<4bsdLq&_44x|wFuuu*u-9mNFm_Zw4XF;~v4D5K&?NOau#@w|Jmv0LOfV=} zY91)goqIOA{H=}RdGfNCV?+5CH30piZOC8h6#zo)Ww7O%8B2CKTg#qykM;6U(u1Xd zg8|iu-Jd61fgr8Oxy^CW``gcp&bl{HX?)!@;}?-qc=Xr$iU$zhy|sNS0F*@P8Z~bz z35<7!z!gIV3BIAFgMOYULW0xFv1=z9xaa{xW;?U7*~YtPRD3T`;xbRo*&bBa!lSr8 z-$7`l%N82olkudsmWmdWH}%ygjN^t2IL;}Y>1o8v1d4d2VN}%xB)pce?f78 zsAX*zw`L6pp$n1+1NfD1kOpkzOlKd|+&qLI2I1WJrC;I17&^g%c6B2~{~?Mb@%xxt zudSIc^mXv;GJ;=p%Z!`nrr}y)uq#MsxHbjuM^vOOE&KMaBvVmtAx95<^s0>ved)`V zLcvxOoF6#1xx!S!^7qadoCyUeIsW(30Z713_2c8$|JUY{r=0(J>yY%Xwh1iLNeulP0t|~oN_xq&zqWA|l z4HDSn@KD}aLzpNicn`=adX{c_O9441a|)~1^bdrRg9}2>M9(?J;0tjsI#kU?VxNF_ z?75P~-H%7$B6sO#%A;y3jk2?izKyQE-@ymPxAp>KGD9b{1ix^Cxq~uR_Y8Nil%Vu(X1Vc)MehhYJ;BzXdiRf`^smh{ea^r%Xm z;I5>1+S?)zvhQdq8Yt40ktx>ZhCb;=a%!T2s0*+gzMI>B{J{Nz;-oY_$7)`j{|OkWz;kvNfXbsflgL8r)UuSi#8!9P1{fluUub{^Obhr-hky9Y0`#kI zgLxb7sfyV^Ke1GVI1c^YyClu@aexlD{bVT?pvg)3p&ACm2w-M_b`U7OxFT@${m-F+ zK`+obgEAh=vI1-m?pG8>SkQ9W`OF=;#v5OyCQ#!l1y_4?sF>O z@&!9(Iu@dtS1!!gh&_e?YL@E;vSQFZTj3YUuy5;7xJux+J8X6}u_*W|j39Jbf?c7% zl7v_xS31B=m%NyRf4Ojg+}Gi*C!`IU7N(%=CYw0_Z%M$g%zK!{4M|#v!u_(YR$Kpp zS}cdG*W7(u>if`$(sGBC?YzIktv zAor19_QiAUP(E7u_vsRG>8LbvB_B%ERYSHrcw$t|Sg|B|TBR%)qr3D%ZjTy{zNcJ@ zBBu955h;6!n?A(xg@_W)MT%qpTAkP$yVzd8gzp0+5DNMqMr!akygqXjbX;oizy9=} zzg!dG!cIzcX9(|J|DXT)ufH^fgFt0Z7rwvx=TEqM5nKrgNlSccL>KRW`=0*}=D#cR z_Y?NtGxNV)x&N-re^=&jrue@W_`kmCZ_f9>Ui*LZrvERl4Ds}c^O&xNj>B*Ib{xoA z>tf^WWh2_{)f(i+)V`N@jNqG1&BlDH@Z5P{yY8+-{GyF(t+qYXb9xCNX^9^F%@o|> zIuk<8+-rr~Bk@}`uiMKx4)*%v!IwbNz5!9+47r8e?%fhXj`$&JRuvN_0X`)Pd=o#$ zO}<%&I*-0LeMh@?htZlah}WfkPhn`HIcJ0M4LpMc*~uV@3q5r+#u#xfhEi|Tr%x*?*?g|I^f?6fTqf~hvImzOSWU`?fWl6ZV?bN{Ubc6o7BPA__`l%XRTCv)7vKe zmR_^0XnHOa9|PuX1bd{3b=9XYXAydR@^03D#|&VYDd^(cCYclcG5^TORj2rkz5RMx zFwvg(o+p`j?xPWGv9tL_!G3qe;+u;tezAT(lu7jMiYc1_v6-5~vTV@;f%E0)sk*B# zYgzr5k&@WC-UU0Aa}5G(-yP$Yh4ccO66} zhQp=65VdRH&Dg;?p6QOp%bC5*TbJx28PZON!uUp(3Sx{^?K`dfm`KF_!u{skgVH2&>4l>*6 zoMgm=Y3gE(n&MMImr0z=j0mBhZtX03DxST@5rSE617Ui@IbwqAla_^SHjdw>!pU!A zvPCilPKMPi_1D6=`xss3yb&E^2~P7p!119)l#!PXpqND;Um_55w>OCPo69}@D`4hz z2dle~+e@5Rj!b{I=`c)SXJ2U>(x33hmfyB>_;8V^UjwV{`kFGY@n)-$y-)0No3=S% zkj+o*gkJ#zE_PZBEl=6sPu}y&u38h_6Rf~PHJkBh7%>@nl)wIfO`iTaxwe&ii!oHR zqU%YBf?S-}8_u_EYpD>QgLyRZs^9XDBcAfNi$lJ-Tv#^5a7X{rdNJd*SC61PN4zf; zwB%J{UdgY|5~s!%zuI36p^Hd3E>XMH_2uK82o+y26~BC6-29M?wF;W-GMRgJoGo6z zxYvvIKlTo|W!=Q^xw6B{Z-)dX^ScqkyPDr!XWhfZVkR8un>8OU@K1o~^CUiwMkE3Q zFGS=vnydO&tfhDoWvBXhRMAli{#y%>_t5PmPhXeZW!%tMU($CpbnLh26U_l6tvFM! zWjd;AGskC)a_O@_bbQWN05haEcH=~1XQ}No<0gutr6kg4*T-P5h^NTyabpGgP_x|wI zE`>1h$eXM(Jj-szsQDMWJ^PP;`i)3Dfkb{UyRh*5RE#fUQRFW0A{1>+;h8F z<^WhRyXHovGeoYEx5dxvp2MJ{CN!}k7yRE=bv3I=mWuhFL&zv*3Yiy^YncD&nyCP83v@XqF5-)s~P9R)3Vd$9g(t+lZ=Aa?~eG47sd?XxFXs z7QWx#D~$te%hl3i;mu~@b3Vg3F0U5+6ix@7XchW9C-iWON=b{uefn|9rZh**db-h+ zbtOe^kBr!1)TEHf2ZfW#_bFT%mRW46SIZ1xoua$;blD&Irdugbi_GjOrdu(Fa;xj{K1YG zqPo9JPyf9b*TV2S?f=8xdxkZ+ecz(k#fA+LQLq8hrGr!(2%#vwcS7$3q=Tqz1w>lt zMQPH7&`Y8sHG$B3?}RGV1QPDb{{QygKRlar?uYx~JkRx89u05a%r)1XV~#P_8yo(1 zpz{vSyuAjhfDv!ESMPMHoVw36Z&LXzUXwxiz_7i{r>IfR4$A;Yw{lBBmk}cZvpM3k z8~X5kn9b*}M{ni3_EZkTzM>%Za0(lE*xeNc*NTn?E$p(#$o5-i349}>%4AVk_f%_M z`E=~f?J?5|pKm?E0{x%5V!wrdE~L`kEJ7DXx|Y>klj?UF#9O3bj$^!CtU#MdMHdk~ zx>euFNR1Bxd~hu~r*f%6x!IbX?ekR$b0dev;+``HCo%7^x>WsDi&wEsan?Mu%i1_AUDqL)zg5~GzQvUj0GR@}(g%ux+72k+*Iufa`t;`7dCkY*1e;}G2eg>(d5&L5(pceP`1$47 z+acExuh=L~$Dm1ma|nz{_zt2IJ7}-l!4)lz z))DMcM7fbHcw*97hN}pHGkqhKtOVr_pE&Q8byH?K%_afMes~9c>CHn$fn3P4vYzUt z;!!;W#t8Y`)(x$F2^M!`Oe1C8}5xc|GtLtR@ADg z#_17Is0`9NzHqL&!5JCm1itpAsne5qS)tgO>x*Gr>oR}#g;BQ#ANUlDqMK|1adUu* zG{YFBqf|BfnCnVTwJuzyBY&+nfx)vc(DwPHGR_r%)|4j(ab7Fk+IH1nA<)~MTc}+k zoxa3!@G-I3`{7x7lgH$cLcBclBc#OR zSgmISO{UWo`U|nUJJ?;(=;KdMYrn=e7Pq6Kyz}_GHH;U~#2z1FPeX~2Cf;fySdczG z+={#-PZBzKZY0|_qE~OuNh+e$2(L*S`n$!O{c|I-L-hCtX-+Lv+LB!p@v0Ms`?1=Q z?0GX;Q^a9&Toi3E0z`ljJsNTztLl zcObGP0A4=jDiBB{((3?1=xHqJvDs%vHLREs?jl8vqAGP8+_;G0?4l|v#ZC|BqDqD0w3 zyw}0={h>|mlYtv#h!|S@o>$2vHjFE6t7=Bfzt5s8tb)OFq))OH>CvlUOQgjlQ--!pa%7XXykR0(w#dqmJQ0P#;Nl!Q=W+x z7o}|C5h9VhOmKz?Q@i%?D*R3Mnn}syqfgb3UEp$AG!vbm^=G`35fdDF_LhHWz%fh3 z3RF&21S!lRTYjpzeMDHNFk}#}Z7W>X4Jz(%CRu~%(Ljjj-=!vf z%41jcQ9g*ELH(?)b(={Le6vu8%0ThoH$B?V1>q3>yi#9Z+@&ev)emC zP#TWhkXpJ$wCY06Gacwx^j;0WzdK#zQIMwF;Ui(;C^HmRp?QOiSTIL}5s;RbC14aq z7K{4D(KtMMNMF>m+HG@dx&qyo6id9`u`H&uTE%!36EvC+z~C);MoFvH^%i@%^XMSQ zPfHt-&lstme>rqCU&vNXxg)cGLf}fXxMhrzu6kj_Fo0jxqlz^i30=)^E5Q_2ub?l# z0;ejnY-|X+<~rywInu+y5aZ7idt09=hG(F*FbkT@DCe~^!Ju!VSgMIm^jH#(R^_)n zTvJBD;fE``o5?AOMic||qp)%4&Q=5i34hS~O7hf0ju{n+h4deNMu8k}xje^C))MCP zQKLm^g!|z%%?;r}t1)TTpd3El+(LIc{4!ZZfMJ8sH4>qz!>}`Yj7D^Hu1GQIvD!+E zI=)FAsU$jnF2wxm<P`okLHEYQ{VF76%-t=?c6)! z>RRlxPqwa-WGOUh%J?%!K)YGL@ADK^UYa&*Cp)5f@T(@|Xn2M0xe8mt;cy0a0a_z^Vy&szOxK9Eo?ZF2^8CS~!#OdhMQznSQrNUBi%g8ni7-dRa=JXL>Q$GDaY8WX!kx!LY@2ilu zR-;pq6Yvi7l@cImnGUj7EF|&Uy6D2!(Lt%{Dd(6Da*Q3wW|c$-)ymM-Ll4)L&Gj?h zfAQIYw-?;097NZ;TZLZ|@+tC}z(jhH;=skgW=XVljm$igAa^5;wG{QKWr>oD66TKi zpxJ64Lp1b9baLH#3r&dWo9=I6+ye@1Pt4k7=@(n10qp#yf?ZXeWA0=_`)ewX-WoXZ zJ|--9W?dW?j zfs08_>yPRwL9w9MZK^lAjYWPcM=^OB0$5%e2&R`rn^^a+<)Hn4OR`oh#81a@3Awbh zD%2cfaBR3yOUA87W|+@5n?&Lsy{iQ={}>Z}SJWJ}G#%G#n(VP@kj)1EmTuC~^s_@g zcL-@*$0HGx*tfAAJE(`7-Sn#i*}ba&_HpnbKtkkFu` zP0)|9G)_snfKV&5EN7|htFX>zg7olEDe&;xAlpXk@0j$a^(peK;@_qk82sfjP4ZN| z+$J?fuFkSCq^0x5ec0w@On|4rl-y9Ew+#&UyhX7R+HI=2YMgg9G)!JM+v6Z~adGTA z4+(#OSBIyaXvBUj&9{(7?$ZVA$GrcX=DrL2iQJ-7ER{_$BmoVrg( zS!8Te1_$wM9~}#kH6~O!p|5i@bBD|kM9-=9Foz<8*8{Bqru5I1DBRK`+&#b0wSi8h zV)OW%rkJkPek=@GkCDrQ4;q)0HLk+uP)*kF*T+&BiYuy2TXN@Jb56F_^`$2EZQb$8 zsPStck3||cvhz$BQ(p!ZG%3BDvsN?^%EK}Zyfbk_r{V0HCIi;DPDg2^VFideQzkEq z%-|o<;`)@ZEXJ+4>*krVy1r@(-#^9iIc{{RhvXbp&4m!w+D8PcOd)t3lbE*3;f=hL z3YzSoti_5?TgsqxnBL!Rh*#-!MWXFKFU%vCnQhC7j6^Ab7SnCYt=-u z;!3tY5qCYO*-2$9u>t7~Gu0#3mTy)OJe+dfHZrzycYlHkZu1$$GFH$~xAEA+4agFCKnzCT>kjVKT<`}) zUMZt?^GlO?Ksw#>)cxj=m!2|1fML9*IpY$BRU&$Ut}L&*mL_7=caRH5CsOTd6m*ct-8+L6E;`!g}1WnU(GZdf+6x;yd_mK%F`ElZsjh; z;tk*C(iK^I20WIltsW+yN>v=u(=~{yj%r6eD-Gx?^QtZM98pdpbcSln zeR|)O3-DiXD2mL9_*aj<6J2cxMXCBn%N)=m2wZq?`qX8pNqyo42J;C*tlKbtH=xJt zr$}(`)^TbM1&lW(UKbyKb&azI~@NN#8m~%I}(th%Iu-N?^3p@8G zo%Dn{V9Sk8eV3`xbErJ?q4>@P0h6O@QeRJTeHCo9>PCf}Y=wGNuT~vqi+QaJN>tSC zU=-GCiT6h#K6DN6OfFv?Cg7Vo?2>^nXfem7-Y&6ayi%ikZI>Ce>_H5FDOP;@lt6tB z+~I)MP(wh_<)&i;sY+WZOlWqfiw;bu5cvXcofFaj@*H z^-uoxv1))e-mwQqc_-%_<*V|NZ^@1ejzD79uW<-ubKcM*$Y-OoO+kA8WI==Y6TAy! zpXxqQPfQ|ZT8=;nCFgVG`?XSRE90wdk19!k`Hk*F66iM<7_wmI6<3X*`n8s2CT9s} zL>BJOwOOujfi{v8p_=d{%;r_QM5#vRNueg>w8fz!xu(nXRSfkQd+SwknO&=max}tg z8~m_VDq%w0)v-E9ym&4>?p^g%)vjQlNqA`2_d+dIXpSoSpS1^9c3AF{vU`2zYSG7o zMHo`1t=!K%*J>*h;j;E(PT`gjo&ly?w=S{u==*rt78WIM@oH19!&svn- zcFsFlQ+LFi(v}LS8gJk*kJzdHZ6H@Pl&{TlC7SsxS8XfTA2E-a(<)_{`d&BRdVq~d zlCD&AjK4>Rw^y3Q>|9qu67f@3+>Pm~cnKX``pX_0dQDd<{Q3O6-PpOo zE{o2NytKGy7b)+q`YDxq4)I|P#Cr?HSB?CzeEEW&WH535#cAvVOlMor9~Mf5(puu4 z0qCLwTIswJ%Whchbw~U&mD6sB+ZGRU(|Hf^p1%|K=u8yYyU~BP)QP?F9x2=)H^6T6PQ=;&GBAU=eb@{v8DMfuePM&6H-mL-UoJc;F)J=jDdd(+(Y z`P{$2$C9ZpBzN&YNY{q;PZ=?G}qMcM227`H{wTG2Nck$SyxpNJe1oEJ8_NZFaduzfAN;`4z?^NkXPE$?l1N}K0 zif3cE{4J03+W5jlABCr9_5_wi-c~J0lmo8pX(#Lv9J!DM>2c6 zFmCghI!;aSAiI9A=9T+5_CU0_bTVnTDH!Y#%X@2aXAR51E)nJT9dYI!_N|bcOBSP- z6pYuRaJelt#;PVia_Rs=Zq&ZdXO~EGDk6{Wr%%I;fQyvfv2cy=sqB=yulGy zY%4mA9~1>)EW=g=y*`7D+c#O<3_Jf+-g2bk9@E)oOl^>T4IWC;(C8pvF%rut5 zrXZL*KwpHne)X}KPF-%BNK1t+WuqpTvYHiU>B2qlM={|Yvj=(wHZ+?kIKDbS7f(m+ z97pq!EM$jW1Hj1eLOFJ6O`sWe6|$#FvONTdh+E?Wvj=Fuy7P9H?he)FQH4J~v4YVLsDx#!#zW8;zy2|Saw6Zdw3IBCJiOdv%%eKLCSO=NKF%tbpn&5Rbi9?zG7}$;j@rV7CJ(}0-J{Wt2%;-t3yE~x1q_4tvcrq z3J%^3T)LFm(S8m)H}vCy>xNID3aXHy_BUO|b-|A=lN>@>vH{IB`$CzQZiZ6v`2(Bk z7ST0KXz&T%gVzzpO1OjNQ-j$oh;h#H$*`{taY9sf6q>Gw@;#^6j-iD)RcUFGi9j2( zp-$${)fcB&n_!}|AqzW+7RG%b32`9_4jZC??0Jj1ca;E9 za0^uft}apDnp_|~G8eU`CUApMp=ag$zjN<_v!9zZ~>S!;Pn0`4o1ADMNrG)teVzrS?D!-V6pW+aHJc>J)G;Wnvz{&y%7P2)X^{JR z3a5xCPo%v{=~{4iNp~;<(zL70{|J!S`2eN{emJgMT8|l`!_;tv6VSfGKK*BNtDuA@ z9rKv(ER^tt4&+~xRYnG7#3zSFI%DR&`KYEiQBo!)ql^atu3 zC>ciRy%fg5&Q)RDznedk=XYE*zZYysTqbxF$%2GnaD_pRM0P*z?{xO24jjQ&w z5y-2Iko!!tPcjD%vF(ZzMIf3W$8bz%*-fi)CBiX)XsRw;7;R)u(y1bCy4Q4SgJ&_`{NcbitNd`xg8SB2GrOdY4|q!SR-fULVK3#d9BH2H4d1S zDAxUh+c9(ejdM{lkb_L37^x2q*Z}RTt0>+k!>u7#efvrfT+=JhVRPTDe>V6h{pAIC zrS%Mq~i1LrydPwP~UZMc~a7nav4sD5uRU(A9GpOjMWi8xd=Yr=d z@)+l8!9P+N1E&w`@Q-({r}Qc~*_%*Uq&K8IXEX;uy>F%;C5ufi4QW~Bb`ffr1`<=3 zZOe)%Tbp|DYe~C0Cf#xb3!pJ73JiDOMzo!0O~YEV$aa8{e_R^+GBP{PRJa{?`;Ur6 zw{*(Z_7aQwb>(+#KN{AwHgnUdxMv3E6kJ&of&4lA^J>&gK90f$P#uktd-oC#_6}pq~lnF#avlH z%L&|FjY6@L4NG6y^A){t%7M0rI!r!FbxenoTM{~xqebrDQi#j* z!`cwxLmX(C+qdaCHI46e;HdXH%kJg^9Bb4{)e3e(g+1Sx^u36I2moR#{Ue`eF>ZTB zA_j0r%gt_n0q(TU>Gt9o@!9)StiW%<#qm@7=;8>V#%w%u$wnG~B&P7TC*-UOTmVcH zFJCTj@$V3V+4@JnOdW5{ifV3;0p67D!1drWTl3Rp>nE9kcyNYJ#pmk{`9nqVDl|!N^YJyN!hpff?EAy1Y|{S@f<6vj zQPF+G9EKfd)BEu@bo;975CSV+`TYAW`hrm`z-y*BHv!RWoWNp}ad&NQ)GjsM3;BSf-8U3P4yw)-La`{CS7K2$J++9oSOJM zY}X^xNh z{4T$LclM3`vg@DP0?%CIlLsxmA31i~?he`VWCTWWefzm}etJO6-|Disl1!XrmY`k8 zRO0|P%0&b1k4$N{7GG6qlbio;&Hxy?5u$!ZdluxNNQ7Gqk2*Zhsboh`gAZMG%po4*QIH4-Akd) zLaTevh>Wn5^cn@qTn#3PV*#KewBif1wWG@24{1^fB07OJbeZF4hvQ*$n8ef76n3nU z_Z>M_%@85|ovsofUf~2j$3q`I9dcetJJpJVwoj2V@Xx}`gwZNzmXAgCM4RfD%WRSq z_RJ7NU@~9#vFVs0r8^LPDAShg14J-8_KEi2C~Maex2lfJs2?Mk34=!j>w8UJ=~$xlSuSHt$t#%L=$~Y4*vsg*k_`ezfV_`;o)mLLMVB(W0;dv3u?U`lMaw>?o~!>Y zRwmmua;CGHKvz1{3>FFYdT^yZ;KGl$MxUt_VOB|4AJW?67cC`VT;kw8-UEyWp))+Z z7uPU=g2=AmKi%3QJQHseaLq+yUF>sjb&t)V@CkKOUjgiY_4rL)i5fiZhxef;y4t7F zwu5?#^3Z|K#UtJ6wlS-5_Lc=a&DNF7qGObgr4&>k0)ZI5eoa}aS5=dYT(rU)KzPh2 za^FSsPtzuM1?R{l#0@K^7n@1x6|+nG3M|!#NQh@yS@e`MxX*s@pPTGbqDi&^ed=+y z1U)C&(jx_ahBGgmBFoml6NJBxm=wo^mXCmm10~*WP_lT(m6jy}mb15FmCXThg!L_Q z3(!s%(H9cML#@@@_^N2V)l(H&{IjwTMIw?q$X>G}cF7gu6$@06cW0*o_3>R$Tdk>S z;e(~jwZcLrO|oYpFpi0h!!+4};IEW17Dnrg-!iqhkQ}Vd%FpJt2uyGnt~q|00G^AR zysfX%e_B{o^WD1|@Eu3ZVcE8fK^4t5wjG~rVZ0Pvl?#P$h}H6Xo?tEV)ddIg-4jIp zDVuPh(Q}~r)S@0FK;N`GY-i*)QP?E@)4+2ZMLgbwWNsI_0XL&&JbNXn#{wP81MHzq+fj>`jFR(#Us_!ts$L}Hy7yg zr9p?!2&3*rR?bGVvgUY@|1K!7CE}>?L=dl~e~AO)6YkraT40!FIumqqCX2dR z&x~x4pviGo(QQFAXW+nY8rpo=HE#xqtw0uE?ctN86MQf3^^(q}sO@8^PHC!v+qbM! zU^aK#q`_vR1h@FHBo!cDy6&8sApy@6hrpnHO(-vKv5<1~Km@ymhM>QhlC-Ckh)HKn z8IVf@PSH16$Ub;0$Ht(}bcC^M?Gl~Md$B>s5pA1d)kVIgmrwxFrVOMMpq#2^wK;w8QtZz@*aQ=lF1WB=qQP~=&6JOfM_wib6Psa0Hqo<8vKgyTohntU56^aMmX{mZJZyVgIeDkv6h>^!tX&t320v32ufr*Z>+ zAFA%0yu1w^WSx=WRvDu-jYkRD_BPJxS`JyOdc>nw3v|A@{XnkL=(97pASBeOT|3UPm+sjSlm6 zahgP7#k#?nCda@o@1~LiS}zK5;yUGOgz-YJC;Xn6k@n??imHig%a^&c=6hJFVoOkS zWE-{t8Ijkfuf*3}XHh}1GLkXP*Dl1GDk7{;iDZfb36Kk@ z^jmWGtg}#95lL<9ip(h^tL^ECulgosZx z<(d5l5jqBX%xD3oN1xbeJBi?Mg}pVb&SoO?Li@2;WWoXwE_s*DqpOPry)s28t(pR< zk^!b#8gx)SW;2V8QqOVmut*FM?wxV?4a`et?hv~0{lo)o6{(0QU%6PKy@)}UnRe?F z;tF9haH5=#75py7cda7Kzyuh^6W4qB2-~1O=sEZ3x^e)cY)B`PbV=Kq`*~h{Bp|F> zu)ZV*FMmpLUS6){w5nq~Z>|G|Crsuvun*TK+kgpngpMjn{m(W#I4DZ+fP4oM^@&y{ zu_?&Tmh%hs5gkfF9-J{tlh=|{Qs}dTZbL!#PoDGSb#m-#(8m?~_W;%;Gb^f%eL{*C zQ=ww?BI2c_{I>`T*0{U35plK-f!cCT{ojpet|gnQDLshx9BWqMk)4?gSEQeh8)eeJ z$O1W+Jav*;Up(fBpiN`Q7t6c%Y*Smxh)c0Lyl8*uOPS3-ve{_4BYc6n^<5Q?ysBQ9 z3YGe{(F_VPG7Bj>HxQPPIdMY^P-CjyW;<`?+xD4pKe{KclqVfl*2HIM!EZ`#gGMS8&E0A8o?x_L!*kia zA57lK*kP9mH4V%0C}Vg@+Wp%}<0v_v=Zv(8}helcGk1!8WBENxxldj9pK)ETVSH(K4h zc|mk-1-|79}#Lc5_?H)OfIjF-3%|YTra);VEFyLW`Mn>q_C} zw(sjN#YAQzCl2POooSU_3p&^j1arDz-zH(aUu4?VY~_slBW;3%_74jH-Ae72B(F?1 z9^Zs$Bmz0YI76}Li220u%v3QTz40DPY}hS!M#x2z+h`*RQ8PE)nLQ0uvJ_~xME&hS z6++zWq`HN)$jFTT*SFo8*Xfwp8*##57||Ov47diZaM`rMDmew7-sGBpf0l#yefnZg zqg?i>i}~8J-1*fPLGCgGt@iyTZK74Y*@H-4tSF)vm^+xmfEFL2sif&;(H$n9HP~aB zohvX835pkl_`=N$2AgaGo8g#Rz-Kyo5)Q2CC@Qb!n3^I^eb1-EyzkLcJ)o5*mQNZt zL`*F$Uqf~~Zh@>$cM~iU6`*sQJ_IY9Yz}u8=`fA@XtIW_2xg_aH=F#}j1WX8>^T00 znKN4Np8U0X4u<#7ZpFw5SjjjhQgG-OA%Y}jqQkgw3#P>?ORz0R;7aa#XCGrKHNvN+ zd1f7)KDkrqHD^Sv%wyW3O|Jz&;$k9V3ZgZA@nKE(&@hRe zcD+oy^kjV>;>Im2OS7ikGUDL*kJW$)q7OP4v!22;uWq*1<|t7WAGf9WZO)SHL6GZa zSGc3$nhP}J_&8V`;+S?v8-(&AuUJL=HKELAV#1!*M_^1w&F%KDr2}7smpgCA9WQ&l znNpJU#vprnG_r|ZH_C6i0}hA}v&T^1RK*OL#_ulxO_uXRTye@Oo$Ibgx7p}c>ivoG zjCGjlmmQ;Qc~g(@0I>zblUWI8O}hOy=jw%5z8tEzE$8<9pfRJDNa)Ns1Fx{wg3j5= zwz_>eaEz~iJUvHR&097O47FP}TE%tsrc9c!viM8aGM#=4c~R7;anU-u_MyxO5ZwSy=6 zIQtlAm^@qx%zQR9@kv{1Oo5$|ZtFDa`IUpj>Y5(y7E-5Y;#DvOp0u@7vLE<)y5KEU zRiFOqT-LhQXc&Z(cHvlLGxyqoNU99Sq#{p4VE=Kw=J{k8IMnHP%*4Oo(rvcRvC!jx zM8ka7z`ofYp?XnxI0x_Byzp&-TAhRP=XgwU%!LTk5kyd8Lwg*g8e^K^FluXEw))-$6)Weu^tZnj2YyVXEYq0-^W6-#l%9h{AiGvKlk zH$xS9vOsfZJ;&Z{e^|?qAX?aaZZcuaQxCuEOX9k(&hMGy^yC`is#*U{tTXOPU?=~s zN$^YpIK8wIyVqiI_WEb%gKPyT7B3;nBZX6s2~ADNmpm#E38_9sxOm}02s}m+($*91 z9lKuY2`W9^`dlj2E&uM0V^QAl9kZj>GqEp2I>vKsT#9D#*i0yn9 zUrb+jI{ie8ynZX$b$ru<*YvtO(>)XL^V0~SR0-zr6ZFJcX{;y^<4Eb9Y_@Y|vPs(C zu>S;5B5P^-0tye!iA)WY($&DNxQLmf9H<65%SrDx>l{E_PN6{ApGdY{?r zz8o~erg~GtuTQ;zfW2XndAs@yP!cYkm|afTy#OwNDrY-yetQLs;`t4H!-XdB0eX*x zw%Ng}&SViZ#z8;_30(O>x;+&?PKvzWv)TTp2h-2Dj)+jJGoQJX4Eso{^5V+_KRJFB zK(``=oUZAQ?0}!~LV4ls`*3acC>(|oQXMt=T~=t%*#3`{bb?sTwszxeaxTV2c|Okf z3y>MqJZB#=XxV1~VQ0qRVK0yjWE-YSnB(ogy{Op)Eq(m^1;r}LhVsXIy5e7lotz<( zK6a)3DKa+TWKhJ_hX&Ng;~kq=YQf1pUdJi&?iczw>K{lxhdOE3_k07KjJ!iW#uvP^ z_~InzW$SNQdpg12A^75XWPPip$Acx0NmBd7pcm*9hB_C63KMP6h>H8^BC9;m-#Q~+RmiTgF?J!icBxz~k*1P`-PZECX< zt!l3-tjUVO%Y3q{dB zr_|T;EqNKO_x6h;6nb$6zO@@^@s09C=0{_bk0}^`PyY+zZXg=WRfTwmFiZ_}88N(pHa096ZQ}eZY12uz9t)l;Hh+-Ui%WlJk#v zKRwb0om%_;%)dh=5RNooie^OXN$@VN*f#lJ*YrPe@KZQ`y!Bq&DOT|7EWX`pt@wT7 z<15Jm!pQ#rE{wlJw@O|(@b6mpFJsv6Ht}m`{M})H-8dj^ zeuI-=YwZ7Eyx-vDH#qqXiT}!h-yQaMhy6Xn?#Y4QZQ@sa1<=Ls8TR)K`pZ$ED_2!AUM^a@^-#&0&)*WlY zq>ROjZGZo9FlMq-re(o%^I_uTSWfY#yTmeSc{`{2?=xzaK%Y~)VDtHnED*^ePsHvk zlAWR?Yi^l9b4&Xe{Px!<{srXufk>A9Mxggm8j$s5k2Ev?szx;vUjROZ_&2CVG@1Roj?A)&i^gq{2W&$6_-*z8SS;Q-7!3zr^o%+L6mj^oN)4|MCL-wR8Ur*6$SD6OR9E z&EJ*p*J0>)<@>kJ@w?&vT5*3j+BZwy|C1bz8dd`{*MxK1NWk&=IrjwXc;JFPGrz`oekPZ1N(HyCA*7 zXhBBn+tckQj+noCftVCmNOEJ^7hFFA@KyK|i{E0x1GbB)He8ZyOhR;819OO2_lwrA z_xXwoJO>W+bEddXEK)AaUhLm|QRI~WeBE+)8}mmtjkFHT=h69()D`jKY_}&}_X+gp zZ2peYK5O?>OELmBxS86gp6p~awnsNRWxL>~GlOzD;y~O0@AB#*} zjO{SXbkn)G_lZXTWM38&7!Izay~^VF%&YK0*h_6OPc!6~>-{yH1$l#*%`O{ev0ZLd zHdjbWBeXrcy3ak_(FQY&K*Af5hX%J5!F5Y`@6FQx^Ras}z+fHBf>Qcem&$7)-&y!F zz>E4PU-x-)vN?!Eo66B8R=-f8SJUf#dARiL{leT2!c2bV$=#b-ZYP@OctbAkbGz3+ z-vT4rjIqjbl{Iq6Qa(-z$ovWf{u2k67;yR`s;$x?^I#YG4m2}yb^8pGenX0qz9rim zo}Fqs!Q_wy_iqje?Q;*{S(pcl5~LQTy3T%@B;Q2WtS|qbK>c$=k_52F=9V3@Sabe- zYo`0fix2<#?>!4BB`4`CU{rL8p2g3Ryu<=ZL!bXospIu{D*k~Jfqf(7)xmu>o@5so zS5;GrT3asOnIhk=2!eO%@qYJ!0x?_PvMVNqagTE)vvv*WMN@hj>;Ai}tLcWJCmF#`X zL(mnF>YpqkgU>Mk&zs$#`>X_opCgFF)TbC_<%2Z|;C6G;wM=;TNnQgLa;B?RTDu7O zjgi-4T652%LjOB6n!ubL$!4Y}Pbs=e4ljS#mAL)hoT31h7^elTsrk>F6VL5aNF3D(z9Cd2mZ_SRMy`v5ymKX2F&-_0A0VHpd`W@$fm-Uq_2dm~dh56qc zTY3W6LXJoq)qOW1kSBj%<*#b{PvU}q`5l)%;rP40{f^7;xct3ge%F_Oug<^Y@;ffS zE9P(H@pqy8`^Epam7(+V!E&9+!oF+cCb;>re?JHh^02ZT%mRWw6zyu>C6yuqx*lHZ zgQY@81z=LUZ&lkWtu>JtQkT`u@Ww}vR?P<31cffkfFHd`=eWWwACw-$`_ZjCv8|{+ z$UISut^Z8F#BOEa-^u;UizlT3a1IgWVKD7W!*oNgTz_zEu?OW1Ed4>^pEvrP&3074 zI_PUlOM{DDrYtd7IpdL+wpC|wsr1D6AKcTQAFdf{&6Bq4qHCyfo5a}n8=;SPz5`m8 zi@PE7k;7pZjN(Y%Ue3ez%GOAH_pD~sZTBeA%jJH43AXAZ!}O;wFh82;Y;3E*4!5~9 z+;4N(2~Sx}KIB5s8{Fq<7RS$i$^)0LNL0%%l*~g(FvQyYT+=sN+Yp?2h#>J>WqU?7 zMWOjRr8|OX@yZqxitbuLBAe!hT2nb{f3Nc|FDe0LQUd86Dta%uDqB|^rAwdSgAy+p zboDuvGxF42c5x@MaT1jPubM8HcscQL!lHggF|&N%0jLd#b9C3d`Z-);7IE)d;%wjW z)9l_9*umSip&V6NakhOPw$7p(EZjf(G9GQ83wZ2RU#8NTwx4kMUJXnrS|TEEaaR^_ zZ(&hHPPK&Hj@d*X#ZxY}c}+{swly)vx3{ z+MS19PMPsLc59$IjdfdQNn(jb@ogiQw_%VE&Y?}n#!|Jr`1pU%TL1mkp6Y^+hl%B? zz4&5aW|p%%#9%ZP!WqYZ~*0oRk)XB6AS~)TgA@ zbGh3XHn$;bMLXvaFGW&UD3MKFdKcH#2CLth)h-bw!1Mn=QnTIa>Cs=UczB^dL{v0A z9wsH{-ol$=*#5n+hdcC_H2!<_J|RIS5YpAyuiq_mbIL)DXKCR5*NoSiOQrHq>({f~ zurX~_Jqwq&U#1vg3%Atw8%ui`!09QN zwiCTK(Z=60nWeovwiWtNasQ4e!99rpYSQh2ORVJihz;?Tr|fRK^+pr=BzlnDVM_^z zO^nZo2-QhUShRU}hJx(&3(d@Cvj`5oJm|IcqStexxd68lxXRo?=*eNj^9}vHp!i2- z{&USBpE~7~ymGhOB^L7k-lqYj`2?&<=+oD#Osyz)dC~PbuJ5rB?{j?|Vvo7hMz}fa zuDaXHiEd0@-d)d-LRVG4O{`=)x(TX^0BNIN!|Q8p8{((m<=!%un{gcOY|1S{9GlP4 zbRRc-iEk5)um7JiVe_(|wW4CqE>ZPIE3GC4b}}RC$<)3?(=QpZGxKe zg!@vpgc@=3qlU+v-P(u(`t+Gg#VC07bRGTfaxnK4BUg}|vhp*hQ#b#jS^r+x|Gdaz z24q}fS|t6gRkcE zP+Q1mt4~@liYAV2Ne%dIXMDTApU1$U2ym8CkPN9d@$hgpy504jvfXRTA;>iJ+kQ8+ zp3~|GMVw>ZT$_-o<1;6zfn*&4x_NJ>$D%uPyKgwQh8aT9)a)M&zy0i88?pv#&Y2Pty2ZBs(%%ziY7?+B>PUZ#M}V<9o1y3viAjrSN#3xF4?(#yGUz)zjfj zm#!wAdeSZbph9O?l4PPSfcH3-FO30(E&YR`6-DHm20nr5-Ok02J-4O_1mg`wt~aLo z+X|#dTHHOUG@{I##1{3NJTXSE>GtEnj;mY;;ibTKN}pWM*SdC6pG26gnuC8dPp?{k z%)ORi_Xb|>^!i%T{4y4`bXT~%tJK-eS`A5^`a=iL&0p}oefVIr$62yb7R3!BX=|1~5uJohN zM(hh{?siMyAs-iSzUKI6_BjXkp^AJZ@qkcSUBr04`~rWxRl7s8BYk^BjLkV%czrZ_ zJ0_&IH-%w$_Cufbq+|K4rnA0v8G) zdD*G=M%!JaaYmH@H{}7EYC>?q53Zg|?A(+Gx3;U;e6lTZ#a1|^eUd)K$-OI|~F%N*t{a&982)K;L>r=BQ$ zWMoFxB{>_(;X^u7U`cM&Icm=>9h+>MYf(V~;y+?$wVw&SjK`M<*5*(lD_6cr?UPgo z4l;sA&d4(H8a}yBiGh2~(c#t4LQ5BfL?H?ZiyL@FDRSYPyyz(S1{>6E3ugB}+ zD+Z*=1ObiUzq|l_d1snSdivzNbosD})E5%HWaZo%E%53R?>ZwrM;1xDTZtOm*&~-k zVS;0t%(Wy5!lr`9)Z~*cYu$ay)Ke=EAg%d^JAH~J8G@Hk^M?uu84_HVJ>@-WW4PxZ zF2B{;HLxZO6yuk@r%>S~fr>Ga4-+!;L*Q;6we&MpoJ^>nue4DyYEx{2(ZR(MD85Um z3xV+OK|*O4UwBU6m06TZS-ScgeIE{Z~91} zawr&jh0bUxL;T7_qN8=r&Qgf+P`(e1SEFFNl%aizd}xeZQOke`G=8l=dbOPf>^daw z^n8V}H@kZm-D-Z>U>pArVQi2WYPoaA`8dvFQzben0E{z@(ZYx}_GLIGK*V>HTUhcWfbVF&PD3iO)Liv>K z5&`8g?bzmp{WhzQ5D?H?E4{2yEpSx8Hpf#@=6wlvn0q4FtUiJl`^o)}$i>BAkCx}A zAiOY(n!%+;9Jub&_x@Rz2r+}zr@ zNk_b0vFY~qtxQJ2yO+UV)=v8j(y_Z>B9Ex0Q<|ff9 za@YkgVwz5$`W{B*DXK=CebKN%W$n7?peW9CrfkLISx>iG*S2=hKHp)%79`zJ?u2P~ z92ItWb68_-4RQ-F)$>HB(dukHy_2)t^J(6XWEf|Gi}++^^G43esY}sC%w@MPF|ok~ zCOwkX)h4!^*t^~{Z$wprx}X0deUm>Hsg5C)kbIgMN> zN0JuLBzX3(87=2~a^hm+vR&=i_5u8ULqLBhiQ}(|ca-K4&1M*&zmN!1k`uzuwRuk( zw%v6w>I|_6{FKdRmaQ5WUUiSVvYgH5t!?vT)E^r@JId?KBX(@7vI>69?#t!e#M5Td z>2Hg3kSk^dKNFTiCPkbFDxp<#{M<7W4D5~>ulKo$0|!F@o$khT;siT!yk~q2>&fj% z9FV}P*oW4intk4{Y&4#k@u*%o>3lP<*`xH#%uYs^{-2YTc0@L{x;oU37~E%~vcUA~ z$B#=1RYr~r1=95kR4Bn+)+S5cY;Q*gcDLn#wfvN;n@uLG2eejo1@_+{6<~unBZe&N zvetaAfJcW-aQAlUIt*gw_?cf&mbz;AYxu%89jnbGd`5pe{XLP5AuGYs$ZXLL3Z%@$LI&#v8 zQHfqeqX@YgTXAfwAGAKOxkVbU%GQI&m1>)tKSPadb-UK$PIC4w^|Ek@iER=FnN#wL z(>`tN54dgu)94GE!;W4=hndWb$33~O5xE_A9UjODPZULiMkHO6`o=V$_X^W=Vc(8x z8@y`kRvSKq?xtY$|I^-ghBcLLQCmhCb%0TjDxxBwROv`4Dgr73qevH`ARsl;g%Bb( zlq#rnh=PLBB0}gPDnjT;4L$S@p$0(Z(IQfM8cB|O*i?&8DXj7FOZ|lF zQ=jD#T1RQSsAT1o*{8+^WBV6gaOyjHph^VH4EOU7#(Xxq}7L)qB5yu>xA zXm~|@qj82Tb31Tl1kZ$5z8z}zAs#8xUM^7f&V9}XGH5a}HoSizsS3TxexU?!A=4iC- z`^5w&>JY6iv$!zFZtQg!4E&_V4BI9`IfchjF%Y@EYJTxIZX9ZCX7qB{HwtFIR)z%# zRZXTp@nS-Beoq6ODa_G?#TPa^MW2Iyp-&U6=LGa?pWlE;O@YFsjven>^(y5vL5*FT zxWL7#2ydJAxHfZg)x~WhWz0i14a8l#VuXd^^i90|s^MejCnKM+STB+r@z6YyiLd8$ zDuo31OWX1JFsb9QF7Bs_C;XAbdmAc+GYe|?Thmh>bzPW}x+;03sswhq+z}09cZA`k zN}#zcipXVDw}MWy66$Myr?Qk+VH>Ken3T9t9q_%m6lF?F9YWlZMOuQL4R+pdP(BchB2rhWnPR_|8_)V&)E$kBkz z+qA835u8f)Lz5PDSTwxp({Wqx{fp0p(a*OF7aj9G4za08m;Uda-mp*=2I*&7NSmKN zEJPs@H`S*%6sVab>7c66Cwt-u(!(z<>q-M{jYG-Y`3mW74P{n=dmPCqsG$`FZ{ z+!wP`y@E!ggxDdEKX82Xdv+}t#3!d}SJEFl>l0UB>+LHR4YWM6)pQ`U8gcd7IH#1| zEdalKRSOHFx&=ttA|f~LFb@iGWhFw}eqvi19j%8B(!B$xmL%y+J<}ckp>vZjw&4|~ zC#y%NVcom~u)^w#ShWS`wOTg5qemU4Or_ zL+5S#3YN;KwM@Y@wZWS=HcaivW4+V^sOYP@x~v;Exz$N5hfxErdmFMb#f%UDHO@4BSur2^} zL45&cp^b;X*y$B9=XEqJ>yT9Q5<6OzD#Q zs75*3ujO1Nr2#8*>7~Z#tUOj98551il!8V*ge7a0q@^@`&)clla;hGeb zyetM+ik{|bw1a4;6|;>eT&BoO?z!U=lQoNBNl&w=9{b+?g>pm%cu|$Iw`UQbcBDai*8y89a|@yX8vnyWQ=12Q0WUi330Zmt1l-`>$|FJDXou#} zu=uJ+VGQbl!u2)iu#?@Y5Wa>P;;7t~WV@Qo_US3Jo?chWE=(T1*0FHL4<>8rMei+K zDT5S5Y?H|k9XJK7LSGYgbbx;FsAl-iToaTJj$rWoDW>wI5s172TAShrV>quX{imLG z1np&89sdI&&2qj_1yXIlMnziCwnag> z9vWeIji@u=VMk1CsRJ0zg34=dGXnfcb#=B(--#1PXMP9ggDbo(&l$~F>t zN)eOx7q5o2uQ?18n57d0bXoz z6!w;`Uo6O$3`&rT0s5*~$p))^8-LZ%t_g1wpl;#ynug(e1Lvs*<6WoCDxLie+i-+N zPOghA-rnxk`h0?S)Bz1f8-=7z+`&bIBYZ&HptUs+lj`!B!^7Q06QO1FvX^25oz$7* zNmp5&pF>*!AH&?PdwLhY1rQBUDtF$M6s;qx4{;;5k^ALz))uD$x?+{=C6NsG zd*fL%wk4XPhc;xPf>>@%0W7y{3y0J0`5ye23cq&hNKI^$(}A8(N*v0@$pW%#wz;k{gMKKFWYDcTRvb+-^k?hF(oF_&CYt*w~QQVZrcC$oQ)1O$C@nToud ztf8XFxj}j9cP-)h)0J_D)!DX~%ksgkxJMX+rPF5%14zO|J{EWMDA6D%N`C*EV9R^W z!opRMt-JGI+YReP_!T~RnaTtbI2>B!pZYzJ=#F8)o0b?Y0|f zr?W(C{aRX9$|!33y7;nKvxdpB>e4D{bLP6U7#w9=9wfn&zeWxRFOLV<9TF6Ic46dh zayecV(zvlvTZ3YH%h11B7`T_hkt<3H8+l44R!BMt9lpmo9lMB_pBt-BDR@5BSETQ5 z!Y9F|Kw|)V&YfJk;R3TQ9TE&kr_t&;xt8wLPFcb04yxHIjS+PW?exk3EJjUKx>q)J zgE10oND3yg0Lt}N7e7y2F&-VFL{8(s)J2KugHhALLMCN;5;sJy&z&Boj`uhi50a}v zd{T$dweP0WH0RJ&`QzfX~=7X z$R)U#clkrAl=HdHNSA9mSz2>=j6nhN*qpUZ2G9kWX`kxvoERYR=TjGXI7LJT7LE%N zOQk4$1L5`l{fKsKiWe2uEL)&`4i>m$)h_A0EvU9kF3SJHCXZQl;(u4Gwh0 zc*N{RGG5+A4vxkM#Rms0^+;-oF^9_WcAae@fz9-0yn!#2kKGnn3~QTbck2x=Xs&#j z`VxG;GN8;pt3Zx?e;~!a2TnYCO$|s>BwfSt9?8_-8(fO77u30c$=iGJxdCgYNVvFU zZZbJl(67aa0*e;EZIx9BhMTR=;{$DQ=i@)31I-81eMhJMFGYj3smjWj@+hS@-+ap}gjzU8A!xlQC zFQMxX4_U|~w~W@fM9JMxSWpdszY9~WKmO`+`)9)7-E;^f&eV}5H_unEtrB^1Px4A5 z2{z6Wx2*{}un11%wjsDD_&VB(A4sBeX&-WPn{XEj!7$Z^AbYPHgPE+sP85&cvsm4( zFL}hp8s4r+F2{`&%(qfve{mV8OeQBD%P7ELe(=%$`z?_FNzvn`?4+FAx`Azo<^0uWl<`Vu_VqkTo|4|;-e>r%mIOk%wFjELJ)5C1uJ@AE;hv(*-`2 z9@?r};r2JfjQJ9XSfyhV7wLxEPHS%sg|nw;*izX?pgowh9S&~t@FEl$M*s@UCB3N5 zXz=>d79w*_cy{`DLd3+4kvSAY)0{bx0n7`Yy%5#5ekCsKwp#m{+rjM+>&Y7 z%u2(TiGhHIk7ZYNYq)f^c3NM~W5fEsoMLS+S>4Om&v=^4II@|#(s-ffgyKUXz@>HSGZwK2mKa>)Nq=pDf0%#`s&sg1 z`m`aa;dO9Z9{pL(iOxSnqdK!DO%n21<_N&u&MpM+wEoH`zy|bBgDu?eKP1(>zV7}& zbtN#nE`&(8z9{k1JGNg6#*(xRwU-MQJ*7Ik1d=MYAEvZ&{sx>sZ~}urSWP08wqCzM zmyUG1`>Y-rEz6&@oj;ETqxiy+=Atm$3lGdc?O`)~J{Jy%{b|>!d++ngw-UTaDXp@W zlVGw4VQJC- zXwS$(OqH4w5NA^t}xm8-;U zDPKp9y%}Ds?*syxc{1qx3?`YL!)~oO6PLru&^Zzd2V~UeF%TNV@}rx!e=y6i6r!`` zRJ;U%g&FSqA@y37&EEPBp#B;fN{ZRvGj+~m%(mIr2I>Hl2siIx0nXPr{8@BNpC|%q zl2wo+3Z5RsxGG#iRf0 z*VYyw6BF^P=nzOyyruxCEOExE9_R(2^MKjcch-!x_OWw%@x~3puTWMa0wxEhzIJeg zd-Y@qOZx!=o{d{hyPI2L58tA}(WB!gn#UzHsju!PK`Ix9{DB((r&J0$8b|cXhv!LG z!Duouk@_))WdY&GWbMd5CGqI5q4}Teyrl-t!HD`Su{nrS@3Gq+u-q6wkO`}8ykrMc zKV$*QXR(OyxvT0MuM*cEPT%0=;w;4h`iie;LPuf}7!pkS=g`)c*!g+k=hyXvTTWih z6(t@N!HcBmyjGnJ%+~Mf1_N{;=ew7a7`?uWoUtw+hNZhJ2br>u(Af*ahjLPjQmO5t5=I9F6Q53l=suD!J^;!|MmNmr#_3{-h zxo+xgkv%Ked5i<~l}Y6W+QByTP85DF0(tOJiiTl9bzo2PU9DTpyEg5f`@k@#>DFdomQ+P9bY6+G^cv<~ zL;rF0LcVvwNp(YG8(>HhGV09MA76KAS9iI7GW%0paZ!d2NQMd;s#%NUO~R6(vNM0L)_MojI~#zuTn{1j-mwde4Wr*oRC z9h=r}JGSlS2J^X6gXs;m+Nl&+{!EIytk3lK$IjYf5^3Z?8pCbn(3@_1iv>1QAO9yH zq%1!0mpQKn)o|#new;zvce(u5^w2RQAjK)n7EkN3TqC*$crMbbc~T4j!tblFRDNN` zJ-_vQOEZh!2TI(k3fx}Aqz2q>gv+1THzsr$V2qKZrDY*c*e{)PM7`>qiNZ}w=2DO`S_ zdMKyoan%0{{d1ecyhfaPD?kCf$@mhoJ zsPw0sG^pTg3U9=*WBD(qMCn}c8q$@}jlAmo#URAjQH=DztX*%!Abfjw}d{rUV=u5lhQ~{s*^Vig}@<&p=_nstPHng)U+s;545EcXU+Tu7eVY8pT)7bcB1w(l$+6)DSy|kBU&> z5PiYj8Ap0Ap4|cRKA`L>0|L9eT<&Z2n`bXCeZHr5WU8S4j-mqS%hu7hqQIaku&@vb zY{9@@ibB%wFo=(KQxFn6t=}3xFuzpK<~3?BBtRd`>8rF)Z3(^xVa;qj1JpRj@%VS0 zD1Otn;LD&Md@(-zUb#346nS^6`@q^77?4*!kx)2WA-pOXe+3(7>f^z&B-&}W7A~+k zA*l~+r|;#iQOAq=X6!zl(qMWyfPe3psivk2EDYXUtxnk-6stQDcwhNy;u+fD*RM3S zRbV9pB4-4!96qwOrI_Bk{Kk9{T(!rw`JXfQbF#n%=K2O=!zU9?X@iJum#)d?v$Q#^ zHWkY9pn|$X6o(jBH}sSS>*cAFI11+s_~Uu^vy6KKp&YD9B+qy>L}CsfJtVHOu#h4# z+LMK|(3-A|T|7EP zwE`lqd_w-o zVaAF~qAN;b1BdRk3-_Dt_9^cTZptrYY+BTUc~f}0-ll-A$fubW9EQ&zW0(Q>6$s19o5(c(z zZYOu%UA4u;BgoOuv(pdppr|s(XXHrHoIjtxd}-wchZJ006d815vC*%{YB}IDJRM|k z`PS>lNAo!r_<;m5tsC_!@MR3L@hxP0XnYfw}2VsAD;(G#0PZDc1cgYvWnjLyXWo7Zfze# zJNX&{$cmhte{LkGBBXwBbIoZy%65x_ekL#-J*_C28iUqhr>n$Hq~yP~^*~Br9_av# zDKThDP=Ss?*M}(8qmFFu-KQ^CtsOBWq^2g+OX={@hUb`*rqcHv zPY7w$d2rU908q~T;p7j_20eo@fARD{lMKGY&uj{ z_wvuzunU5J*5?1^xcS0_kr)n`oO$&J$QPnXzcecTaq-1DC#lHe5>IuX*Pplg?O|N; zXhw^o-&m@n_?DacWSC)8z9_Gh>gfxKv9i08>%%8h`3fIj5LY?>l&nkB>!=RMU0K#l zpr$qjE+JaS=iMV0HHoC@;A%99PQv2t8F>Fuz{QcMqG|)kzCV%%X+WkO;mEPLx`Zq{$3<_9kb$Sk4;Qi%S-kSi* zmpQxG9!?2PQm6*76s+}kuh%_Q2CC(V@CU@t>lkaZPG~TgNl=1v^#9UL|7A^B%k{@S zaJgUA{-FNN+EIY7Vs+#1UW@x?1YGVz*ci{^tVz0`BL&g3H}Q zJh#ZkCBXXRg!Hj1du|y+~$TV1J>sW^4sgPJT+n5G0%A<>pon|%rH!lGq8n_c8%9X zMPS?r44nHMVsmLIP)A?u=a>!Hb2KQ5epP2hhWc@g4fIT3k+V_%Onaumpm&q3*G9Jm z!R&>9>)GjxrlzJ1$=pXZEqQk9u`9!&+dvlRp04iFv%viJgHihBpqT#m%ouHsYn^-(u8mQNl>^LFq1EM5X54v)Q(Mpr5F$y#?=HzYIV z&gGsh> z&~{1lQ)kaVh|kWlWBq7q>(hr?tA2bdf->cxzTq~c2{w6hi#>Q?Sq}W8~G45oNC`~1BSZa3a}P& z^B$W;{(~P}NJa0{ZTAdwaK3;CN*iI{maZj!0Hx|-t+`q+mUM>ei29G<5^D`-4<>`Y zf@0DngVtvbN~ZZ<|4mTwq=w(ztB-c+JNt1=I%!Jfs86#(i`M$$fLev$eEdhzM{Glb z4Av8a3%s3f*|bA8?MVrECWj&-tra z4A>zC9q=~Yob~H|CHGgx4#k`{%~B3CF{tn-4T}^xwShN!=E3>yCSYCT(rJ6i4@X z-r`5FZNG-x%Rtwat-yVMswAxiHA3SYPfovPC|;Z(y*4OLmTB*V}{K5wd?WMI>cz1ub93Fi-ynVm!3!~ z?E#>osPhB{!1EN`4zsf#%2-WMr>}Q5O7Pp88@`@}lwy#8iB@7HRtKl9&u z{Sa#k1|<{z;K%c!Vfy@6BWTHD*{Iz$(tsgk9W|#IeLw9qGi88kg76wY?JEl21C3!X%`b@osbIfwb z;tb_ZQ)F96Am978)QG`FwQ4=quC6V~DheUAtvHnA)x_U(Ezol17Df-^wTlvmd2?%D zx55XCT})-KzRlU^+_`Ykn5c(JlNwsOff^?8G zhAbAx!Nv3=pYc9*#HQaLdoG~lbhV*)z0M|RZShBLGAKc5CAYpl%#*#LqK(+Ei0EhQ z>i^X@73eVI3-gQy4A?V~f!oN3)dGyLD`60zNUdlsaxar;a@z)NhrLpVSbiiL$)a&d zi$?<|Vz&mTwJj7$74rqs2fpR(`(ZtY?D}vmiK-F&ZF#iDp*=&-z}A3Ga|WWHW#TCr zP@y$j@45TBRm?C^zH0dbDD|b^G0tu2vD@nFLDa?4Oh7u5X#8?F1waXvO7bBhHfzl+ zY|XsU;5M;{_~8qX^<-^Ckc@4GvZ)ChuO3MA4c3!wLwt-Va6SCSOk+SsRvl{Pl2wYfjZz1cMqP2%DZeCMF=cD4? z^IYXJNrQlXSfjx9J!F&ov5*9r8b#z9iOS@t*_i6e|VEd!$hME z<*~)Ccsg~{fK^oMm@6^J$f7pw%83w_o&EntKr1W=r`)j$Yggqxp{%&31TORzvZ6DG z;B<`L8ovMs0?Q&hpMJE=-66ZvZ?5ZOv&-64OYHE3#yWUvm1A|nFq!g%4G%PmzTm9j z*!b%6G?v`&<^>#HuLsc*=L}IV765;;!g7cPpOLFv_?)xJTthK~->6J#94s zCnLDs0t->{tXwSlJPVS@r2~*OhuZMN;%i~Q2_70K@rVGVrnJZW-KTpK^|6lQc7cWe^#rs>r3m@)ZmkvG`2u{fh+_4nYPy zz|t0i1pmeyvSfD|r&wna%s?z_7;b`Z%=YJmY}JT@FH~wYUtjMu!3#R4Uh~Hp#2hX3 z9ICj4;lgnp)fD&>Lz0xT7fN~Wy?>M2Vdn85ekyB+>>iges0$~N(i$*^B_UvaFdU@? zX5U}z61%Ia25pq0YNlzE|I{W=nO$W3aUPrr(3YVW3LcIFJ~|3ucY-E$E*D*}RbnaR z+oD4Fj@=x#DI{FW)UTyXqeLZ9!QSsiWDsPp#ePieHrf}v-eK6DZGsBNIqv(B4qqx4 z(D1!mJH5U%1d*#+{wAOue1CneTbe-P2)kurm3(Qr0?A2$2vaf zkwUovsy6@3*S^lJt+YcD|5^m<7&1x&0|yX8N5HLE?LFR zp0>4O&}}*=iOBeDnr&=ghQGt6OF4H;6hwq}G^f@3_?^~<&M(1(1l85AYFdkQTFD5d zm3rp(1!WZ#Oaq*7@EtDtb>KA z2S%@^U!B+;5f4lt4xxNz&?{{dLudI10c literal 0 HcmV?d00001 From 88d16489ce654ecfd1231a7b4adca25cf5a5230e Mon Sep 17 00:00:00 2001 From: mickychetta Date: Fri, 29 Apr 2022 22:31:04 +0000 Subject: [PATCH 2/3] created new construct --- .../aws-fargate-secretsmanager/.eslintignore | 4 + .../aws-fargate-secretsmanager/.gitignore | 15 + .../aws-fargate-secretsmanager/.npmignore | 21 + .../aws-fargate-secretsmanager/lib/index.ts | 197 +++ .../aws-fargate-secretsmanager/package.json | 104 ++ .../test/fargate-secretsmanager.test.ts | 639 +++++++++ .../integ.existing-resources.expected.json | 1178 ++++++++++++++++ .../test/integ.existing-resources.ts | 54 + .../test/integ.new-resources.expected.json | 1188 +++++++++++++++++ .../test/integ.new-resources.ts | 40 + 10 files changed, 3440 insertions(+) create mode 100644 source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/.eslintignore create mode 100644 source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/.gitignore create mode 100644 source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/.npmignore create mode 100644 source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/lib/index.ts create mode 100644 source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/package.json create mode 100644 source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/test/fargate-secretsmanager.test.ts create mode 100644 source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/test/integ.existing-resources.expected.json create mode 100644 source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/test/integ.existing-resources.ts create mode 100644 source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/test/integ.new-resources.expected.json create mode 100644 source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/test/integ.new-resources.ts diff --git a/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/.eslintignore b/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/.eslintignore new file mode 100644 index 000000000..e6f7801ea --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/.eslintignore @@ -0,0 +1,4 @@ +lib/*.js +test/*.js +*.d.ts +coverage diff --git a/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/.gitignore b/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/.gitignore new file mode 100644 index 000000000..6773cabd2 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/.gitignore @@ -0,0 +1,15 @@ +lib/*.js +test/*.js +*.js.map +*.d.ts +node_modules +*.generated.ts +dist +.jsii + +.LAST_BUILD +.nyc_output +coverage +.nycrc +.LAST_PACKAGE +*.snk \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/.npmignore b/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/.npmignore new file mode 100644 index 000000000..f66791629 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/.npmignore @@ -0,0 +1,21 @@ +# Exclude typescript source and config +*.ts +tsconfig.json +coverage +.nyc_output +*.tgz +*.snk +*.tsbuildinfo + +# Include javascript files and typescript declarations +!*.js +!*.d.ts + +# Exclude jsii outdir +dist + +# Include .jsii +!.jsii + +# Include .jsii +!.jsii \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/lib/index.ts new file mode 100644 index 000000000..3bbbdb79d --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/lib/index.ts @@ -0,0 +1,197 @@ +/** + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +import * as ec2 from "@aws-cdk/aws-ec2"; +import * as secretsmanager from "@aws-cdk/aws-secretsmanager"; +// Note: To ensure CDKv2 compatibility, keep the import statement for Construct separate +import { Construct } from "@aws-cdk/core"; +import * as defaults from "@aws-solutions-constructs/core"; +import * as ecs from "@aws-cdk/aws-ecs"; + +export interface FargateToSecretsmanagerProps { + /** + * Whether the construct is deploying a private or public API. This has implications for the VPC deployed + * by this construct. + * + * @default - none + */ + readonly publicApi: boolean; + /** + * Optional custom properties for a VPC the construct will create. This VPC will + * be used by the new Fargate service the construct creates (that's + * why targetGroupProps can't include a VPC). Providing + * both this and existingVpc is an error. An Secrets Manager Interface + * endpoint will be included in this VPC. + * + * @default - none + */ + readonly vpcProps?: ec2.VpcProps; + /** + * An existing VPC in which to deploy the construct. Providing both this and + * vpcProps is an error. If the client provides an existing Fargate service, + * this value must be the VPC where the service is running. An Secrets Manager Interface + * endpoint will be added to this VPC. + * + * @default - none + */ + readonly existingVpc?: ec2.IVpc; + /** + * Optional properties to create a new ECS cluster + */ + readonly clusterProps?: ecs.ClusterProps; + /** + * The arn of an ECR Repository containing the image to use + * to generate the containers + * + * format: + * arn:aws:ecr:[region]:[account number]:repository/[Repository Name] + */ + readonly ecrRepositoryArn?: string; + /** + * The version of the image to use from the repository + * + * @default - 'latest' + */ + readonly ecrImageVersion?: string; + /* + * Optional props to define the container created for the Fargate Service + * + * defaults - fargate-defaults.ts + */ + readonly containerDefinitionProps?: ecs.ContainerDefinitionProps | any; + /* + * Optional props to define the Fargate Task Definition for this construct + * + * defaults - fargate-defaults.ts + */ + readonly fargateTaskDefinitionProps?: ecs.FargateTaskDefinitionProps | any; + /** + * Optional values to override default Fargate Task definition properties + * (fargate-defaults.ts). The construct will default to launching the service + * is the most isolated subnets available (precedence: Isolated, Private and + * Public). Override those and other defaults here. + * + * defaults - fargate-defaults.ts + */ + readonly fargateServiceProps?: ecs.FargateServiceProps | any; + /** + * A Fargate Service already instantiated (probably by another Solutions Construct). If + * this is specified, then no props defining a new service can be provided, including: + * existingImageObject, ecrImageVersion, containerDefintionProps, fargateTaskDefinitionProps, + * ecrRepositoryArn, fargateServiceProps, clusterProps, existingClusterInterface. If this value + * is provided, then existingContainerDefinitionObject must be provided as well. + * + * @default - none + */ + readonly existingFargateServiceObject?: ecs.FargateService; + /* + * A container definition already instantiated as part of a Fargate service. This must + * be the container in the existingFargateServiceObject. + * + * @default - None + */ + readonly existingContainerDefinitionObject?: ecs.ContainerDefinition; + /** + * Existing instance of Secret object, providing both this and secretProps will cause an error. + * + * @default - Default props are used + */ + readonly existingSecretObj?: secretsmanager.Secret; + /** + * Optional user-provided props to override the default props for the Secret. + * + * @default - Default props are used + */ + readonly secretProps?: secretsmanager.SecretProps; + + /** + * Optional Access granted to the Fargate service for the secret. 'Read' or 'ReadWrite + * + * @default - 'Read' + */ + readonly grantWriteAccess?: string + /** + * Optional Name for container environment variable containing the ARN of the secret. + * + * @default - SECRET_ARN + */ + readonly secretEnvironmentVariableName?: string; +} + +export class FargateToSecretsmanager extends Construct { + public readonly vpc: ec2.IVpc; + public readonly service: ecs.FargateService; + public readonly container: ecs.ContainerDefinition; + public readonly secret: secretsmanager.Secret; + + constructor(scope: Construct, id: string, props: FargateToSecretsmanagerProps) { + super(scope, id); + defaults.CheckProps(props); + defaults.CheckFargateProps(props); + + // Other permissions for constructs are accepted as arrays, turning grantWriteAccess into + // an array to use the same validation function. + if (props.grantWriteAccess) { + const allowedPermissions = ['READ', 'READWRITE']; + defaults.CheckListValues(allowedPermissions, [props.grantWriteAccess.toUpperCase()], 'grantWriteAccess'); + } + + this.vpc = defaults.buildVpc(scope, { + existingVpc: props.existingVpc, + defaultVpcProps: props.publicApi ? defaults.DefaultPublicPrivateVpcProps() : defaults.DefaultIsolatedVpcProps(), + userVpcProps: props.vpcProps, + constructVpcProps: { enableDnsHostnames: true, enableDnsSupport: true } + }); + + defaults.AddAwsServiceEndpoint(scope, this.vpc, defaults.ServiceEndpointTypes.SECRETS_MANAGER); + + if (props.existingFargateServiceObject) { + this.service = props.existingFargateServiceObject; + // CheckFargateProps confirms that the container is provided + this.container = props.existingContainerDefinitionObject!; + } else { + [this.service, this.container] = defaults.CreateFargateService( + scope, + id, + this.vpc, + props.clusterProps, + props.ecrRepositoryArn, + props.ecrImageVersion, + props.fargateTaskDefinitionProps, + props.containerDefinitionProps, + props.fargateServiceProps + ); + } + + if (props.existingSecretObj) { + this.secret = props.existingSecretObj; + } else { + this.secret = defaults.buildSecretsManagerSecret(this, 'secret', props.secretProps); + } + + // Enable read permissions for the Fargate service role by default + this.secret.grantRead(this.service.taskDefinition.taskRole); + + if (props.grantWriteAccess) { + const permission = props.grantWriteAccess.toUpperCase(); + + if (permission === 'READWRITE') { + this.secret.grantWrite(this.service.taskDefinition.taskRole); + } + } + + // Configure environment variables + const secretEnvironmentVariableName = props.secretEnvironmentVariableName || 'SECRET_ARN'; + this.container.addEnvironment(secretEnvironmentVariableName, this.secret.secretArn); + } +} diff --git a/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/package.json b/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/package.json new file mode 100644 index 000000000..d0778676b --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/package.json @@ -0,0 +1,104 @@ +{ + "name": "@aws-solutions-constructs/aws-fargate-secretsmanager", + "version": "0.0.0", + "description": "CDK Constructs for AWS Fargate to Amazon Secrets Manager integration", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/awslabs/aws-solutions-constructs.git", + "directory": "source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager" + }, + "author": { + "name": "Amazon Web Services", + "url": "https://aws.amazon.com", + "organization": true + }, + "license": "Apache-2.0", + "scripts": { + "build": "tsc -b .", + "lint": "eslint -c ../eslintrc.yml --ext=.js,.ts . && tslint --project .", + "lint-fix": "eslint -c ../eslintrc.yml --ext=.js,.ts --fix .", + "test": "jest --coverage", + "clean": "tsc -b --clean", + "watch": "tsc -b -w", + "integ": "cdk-integ", + "integ-no-clean": "cdk-integ --no-clean", + "integ-assert": "cdk-integ-assert", + "jsii": "jsii", + "jsii-pacmak": "jsii-pacmak", + "build+lint+test": "npm run jsii && npm run lint && npm test && npm run integ-assert", + "snapshot-update": "npm run jsii && npm test -- -u && npm run integ-assert" + }, + "jsii": { + "outdir": "dist", + "targets": { + "java": { + "package": "software.amazon.awsconstructs.services.fargatesecretsmanager", + "maven": { + "groupId": "software.amazon.awsconstructs", + "artifactId": "fargatesecretsmanager" + } + }, + "dotnet": { + "namespace": "Amazon.SolutionsConstructs.AWS.FargateSecretsmanager", + "packageId": "Amazon.SolutionsConstructs.AWS.FargateSecretsmanager", + "signAssembly": true, + "iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png" + }, + "python": { + "distName": "aws-solutions-constructs.aws-fargate-secretsmanager", + "module": "aws_solutions_constructs.aws_fargate_secretsmanager" + } + } + }, + "dependencies": { + "@aws-cdk/core": "0.0.0", + "@aws-cdk/aws-ec2": "0.0.0", + "@aws-cdk/aws-secretsmanager": "0.0.0", + "@aws-cdk/aws-ecs": "0.0.0", + "@aws-solutions-constructs/core": "0.0.0", + "constructs": "^3.2.0" + }, + "devDependencies": { + "@aws-cdk/assert": "0.0.0", + "@aws-cdk/core": "0.0.0", + "@aws-cdk/aws-ec2": "0.0.0", + "@aws-cdk/aws-secretsmanager": "0.0.0", + "@aws-cdk/aws-ecs": "0.0.0", + "@types/jest": "^26.0.22", + "@aws-solutions-constructs/core": "0.0.0", + "@types/node": "^10.3.0", + "constructs": "3.2.0" + }, + "jest": { + "moduleFileExtensions": [ + "js" + ], + "coverageReporters": [ + "text", + [ + "lcov", + { + "projectRoot": "../../../../" + } + ] + ] + }, + "peerDependencies": { + "@aws-cdk/core": "0.0.0", + "@aws-cdk/aws-ec2": "0.0.0", + "@aws-cdk/aws-secretsmanager": "0.0.0", + "@aws-cdk/aws-ecs": "0.0.0", + "@aws-solutions-constructs/core": "0.0.0", + "constructs": "^3.2.0" + }, + "keywords": [ + "aws", + "cdk", + "awscdk", + "AWS Solutions Constructs", + "Amazon Secrets Manager", + "AWS Fargate" + ] +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/test/fargate-secretsmanager.test.ts b/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/test/fargate-secretsmanager.test.ts new file mode 100644 index 000000000..7b94f6908 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/test/fargate-secretsmanager.test.ts @@ -0,0 +1,639 @@ +/** + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +import '@aws-cdk/assert/jest'; +import * as defaults from '@aws-solutions-constructs/core'; +import * as cdk from "@aws-cdk/core"; +import { FargateToSecretsmanager } from "../lib"; +import * as ecs from '@aws-cdk/aws-ecs'; +import { buildSecretsManagerSecret } from '@aws-solutions-constructs/core'; + +const clusterName = "custom-cluster-name"; +const containerName = "custom-container-name"; +const serviceName = "custom-service-name"; +const familyName = "family-name"; +const secretName = 'custom-name'; +const envName = 'CUSTOM_SECRET_ARN'; + +test('New service/new secret, public API, new VPC', () => { + const stack = new cdk.Stack(); + const publicApi = true; + + const construct = new FargateToSecretsmanager(stack, 'test-construct', { + publicApi, + ecrRepositoryArn: defaults.fakeEcrRepoArn, + vpcProps: { cidr: '172.0.0.0/16' }, + clusterProps: { clusterName }, + containerDefinitionProps: { containerName }, + fargateTaskDefinitionProps: { family: familyName }, + fargateServiceProps: { serviceName }, + secretProps: { + secretName + }, + }); + + expect(construct.vpc !== null); + expect(construct.service !== null); + expect(construct.container !== null); + expect(construct.secret !== null); + + expect(stack).toHaveResourceLike("AWS::ECS::Service", { + ServiceName: serviceName, + LaunchType: 'FARGATE', + DesiredCount: 2, + DeploymentConfiguration: { + MaximumPercent: 150, + MinimumHealthyPercent: 75 + }, + PlatformVersion: ecs.FargatePlatformVersion.LATEST, + }); + + expect(stack).toHaveResourceLike("AWS::ECS::Cluster", { + ClusterName: clusterName + }); + + expect(stack).toHaveResourceLike("AWS::SecretsManager::Secret", { + Name: secretName + }); + + expect(stack).toHaveResourceLike("AWS::IAM::Policy", { + PolicyDocument: { + Statement: [ + { + Action: [ + "secretsmanager:GetSecretValue", + "secretsmanager:DescribeSecret" + ], + Effect: "Allow", + Resource: { + Ref: "testconstructsecret1A43460A" + } + } + ] + } + }); + + expect(stack).toHaveResourceLike("AWS::ECS::TaskDefinition", { + Family: familyName, + ContainerDefinitions: [ + { + Essential: true, + Image: { + "Fn::Join": [ + "", + [ + "123456789012.dkr.ecr.us-east-1.", + { + Ref: "AWS::URLSuffix" + }, + "/fake-repo:latest" + ] + ] + }, + MemoryReservation: 512, + Name: containerName, + PortMappings: [ + { + ContainerPort: 8080, + Protocol: "tcp" + } + ] + } + ] + }); + + expect(stack).toHaveResourceLike("AWS::EC2::VPC", { + CidrBlock: '172.0.0.0/16' + }); + + expect(stack).toHaveResource("AWS::EC2::VPCEndpoint", { + ServiceName: { + "Fn::Join": [ + "", + [ + "com.amazonaws.", + { + Ref: "AWS::Region" + }, + ".secretsmanager" + ] + ] + } + }); + + // Confirm we created a Public/Private VPC + expect(stack).toHaveResourceLike('AWS::EC2::InternetGateway', {}); + expect(stack).toCountResources('AWS::EC2::VPC', 1); + expect(stack).toCountResources('AWS::SecretsManager::Secret', 1); + expect(stack).toCountResources('AWS::ECS::Service', 1); +}); + +test('New service/new secret, private API, new VPC', () => { + const stack = new cdk.Stack(); + const publicApi = false; + + new FargateToSecretsmanager(stack, 'test-construct', { + publicApi, + ecrRepositoryArn: defaults.fakeEcrRepoArn, + vpcProps: { cidr: '172.0.0.0/16' }, + secretProps: { + secretName + }, + grantWriteAccess: 'readwrite', + }); + + expect(stack).toHaveResourceLike("AWS::ECS::Service", { + LaunchType: 'FARGATE', + DesiredCount: 2, + DeploymentConfiguration: { + MaximumPercent: 150, + MinimumHealthyPercent: 75 + }, + PlatformVersion: ecs.FargatePlatformVersion.LATEST, + }); + + expect(stack).toHaveResourceLike("AWS::ECS::TaskDefinition", { + ContainerDefinitions: [ + { + Environment: [ + { + Name: "SECRET_ARN", + Value: { + Ref: "testconstructsecret1A43460A" + } + }, + ], + Essential: true, + Image: { + "Fn::Join": [ + "", + [ + "123456789012.dkr.ecr.us-east-1.", + { + Ref: "AWS::URLSuffix" + }, + "/fake-repo:latest" + ] + ] + }, + MemoryReservation: 512, + Name: "test-construct-container", + PortMappings: [ + { + ContainerPort: 8080, + Protocol: "tcp" + } + ] + } + ] + }); + + expect(stack).toHaveResourceLike("AWS::SecretsManager::Secret", { + Name: secretName, + }); + + expect(stack).toHaveResourceLike("AWS::EC2::VPC", { + CidrBlock: '172.0.0.0/16' + }); + + expect(stack).toHaveResourceLike("AWS::IAM::Policy", { + PolicyDocument: { + Statement: [ + { + Action: [ + "secretsmanager:GetSecretValue", + "secretsmanager:DescribeSecret" + ], + Effect: "Allow", + Resource: { + Ref: "testconstructsecret1A43460A" + } + }, + { + Action: [ + "secretsmanager:PutSecretValue", + "secretsmanager:UpdateSecret" + ], + Effect: "Allow", + Resource: { + Ref: "testconstructsecret1A43460A" + } + } + ], + Version: "2012-10-17" + } + }); + + expect(stack).toHaveResource("AWS::EC2::VPCEndpoint", { + ServiceName: { + "Fn::Join": [ + "", + [ + "com.amazonaws.", + { + Ref: "AWS::Region" + }, + ".secretsmanager" + ] + ] + } + }); + + // Confirm we created an Isolated VPC + expect(stack).not.toHaveResourceLike('AWS::EC2::InternetGateway', {}); + expect(stack).toCountResources('AWS::EC2::VPC', 1); + expect(stack).toCountResources('AWS::SecretsManager::Secret', 1); + expect(stack).toCountResources('AWS::ECS::Service', 1); +}); + +test('New service/existing secret, private API, existing VPC', () => { + const stack = new cdk.Stack(); + const publicApi = false; + + const existingVpc = defaults.getTestVpc(stack, publicApi); + + const existingSecretObj = defaults.buildSecretsManagerSecret(stack, 'secret', { + secretName + }); + + new FargateToSecretsmanager(stack, 'test-construct', { + publicApi, + existingVpc, + existingSecretObj, + ecrRepositoryArn: defaults.fakeEcrRepoArn, + }); + + expect(stack).toHaveResourceLike("AWS::ECS::Service", { + LaunchType: 'FARGATE', + DesiredCount: 2, + DeploymentConfiguration: { + MaximumPercent: 150, + MinimumHealthyPercent: 75 + }, + PlatformVersion: ecs.FargatePlatformVersion.LATEST, + }); + + expect(stack).toHaveResourceLike("AWS::ECS::TaskDefinition", { + ContainerDefinitions: [ + { + Environment: [ + { + Name: "SECRET_ARN", + Value: { + Ref: "secret4DA88516" + } + }, + ], + Essential: true, + Image: { + "Fn::Join": [ + "", + [ + "123456789012.dkr.ecr.us-east-1.", + { + Ref: "AWS::URLSuffix" + }, + "/fake-repo:latest" + ] + ] + }, + MemoryReservation: 512, + Name: "test-construct-container", + PortMappings: [ + { + ContainerPort: 8080, + Protocol: "tcp" + } + ] + } + ] + }); + + expect(stack).toHaveResourceLike("AWS::SecretsManager::Secret", { + Name: secretName + }); + expect(stack).toHaveResourceLike("AWS::EC2::VPC", { + CidrBlock: '172.168.0.0/16' + }); + + expect(stack).toHaveResourceLike("AWS::IAM::Policy", { + PolicyDocument: { + Statement: [ + { + Action: [ + "secretsmanager:GetSecretValue", + "secretsmanager:DescribeSecret" + ], + Effect: "Allow", + Resource: { + Ref: "secret4DA88516" + } + } + ] + } + }); + + expect(stack).toHaveResource("AWS::EC2::VPCEndpoint", { + ServiceName: { + "Fn::Join": [ + "", + [ + "com.amazonaws.", + { + Ref: "AWS::Region" + }, + ".secretsmanager" + ] + ] + } + }); + + // Confirm we created an Isolated VPC + expect(stack).not.toHaveResourceLike('AWS::EC2::InternetGateway', {}); + expect(stack).toCountResources('AWS::EC2::VPC', 1); + expect(stack).toCountResources('AWS::ECS::Service', 1); + expect(stack).toCountResources('AWS::SecretsManager::Secret', 1); +}); + +test('Existing service/new secret, public API, existing VPC', () => { + const stack = new cdk.Stack(); + const publicApi = true; + + const existingVpc = defaults.getTestVpc(stack); + + const [testService, testContainer] = defaults.CreateFargateService(stack, + 'test', + existingVpc, + undefined, + defaults.fakeEcrRepoArn, + undefined, + undefined, + undefined, + { serviceName }); + + new FargateToSecretsmanager(stack, 'test-construct', { + publicApi, + existingFargateServiceObject: testService, + existingContainerDefinitionObject: testContainer, + existingVpc, + secretEnvironmentVariableName: envName, + secretProps: { + secretName, + }, + grantWriteAccess: 'readwrite' + }); + + expect(stack).toHaveResourceLike("AWS::ECS::Service", { + ServiceName: serviceName + }); + + expect(stack).toHaveResourceLike("AWS::ECS::TaskDefinition", { + ContainerDefinitions: [ + { + Environment: [ + { + Name: envName, + Value: { + Ref: "testconstructsecret1A43460A" + } + } + ], + Essential: true, + Image: { + "Fn::Join": [ + "", + [ + "123456789012.dkr.ecr.us-east-1.", + { + Ref: "AWS::URLSuffix" + }, + "/fake-repo:latest" + ] + ] + }, + MemoryReservation: 512, + Name: "test-container", + PortMappings: [ + { + ContainerPort: 8080, + Protocol: "tcp" + } + ] + } + ] + }); + + expect(stack).toHaveResourceLike("AWS::SecretsManager::Secret", { + Name: secretName, + }); + + expect(stack).toHaveResourceLike("AWS::EC2::VPC", { + CidrBlock: '172.168.0.0/16' + }); + + expect(stack).toHaveResourceLike("AWS::IAM::Policy", { + PolicyDocument: { + Statement: [ + { + Action: [ + "secretsmanager:GetSecretValue", + "secretsmanager:DescribeSecret" + ], + Effect: "Allow", + Resource: { + Ref: "testconstructsecret1A43460A" + } + }, + { + Action: [ + "secretsmanager:PutSecretValue", + "secretsmanager:UpdateSecret" + ], + Effect: "Allow", + Resource: { + Ref: "testconstructsecret1A43460A" + } + } + ], + Version: "2012-10-17" + } + }); + + expect(stack).toHaveResource("AWS::EC2::VPCEndpoint", { + ServiceName: { + "Fn::Join": [ + "", + [ + "com.amazonaws.", + { + Ref: "AWS::Region" + }, + ".secretsmanager" + ] + ] + } + }); + + // Confirm we created a Public/Private VPC + expect(stack).toHaveResourceLike('AWS::EC2::InternetGateway', {}); + expect(stack).toCountResources('AWS::EC2::VPC', 1); + expect(stack).toCountResources('AWS::ECS::Service', 1); + expect(stack).toCountResources('AWS::SecretsManager::Secret', 1); +}); + +test('Existing service/existing secret, private API, existing VPC', () => { + const stack = new cdk.Stack(); + const publicApi = false; + + const existingVpc = defaults.getTestVpc(stack, publicApi); + + const [testService, testContainer] = defaults.CreateFargateService(stack, + 'test', + existingVpc, + undefined, + defaults.fakeEcrRepoArn, + undefined, + undefined, + undefined, + { serviceName }); + + const existingSecretObj = defaults.buildSecretsManagerSecret(stack, 'secret', { + secretName + }); + + new FargateToSecretsmanager(stack, 'test-construct', { + publicApi, + existingFargateServiceObject: testService, + existingContainerDefinitionObject: testContainer, + existingVpc, + existingSecretObj + }); + + expect(stack).toHaveResourceLike("AWS::ECS::Service", { + ServiceName: serviceName, + }); + + expect(stack).toHaveResourceLike("AWS::ECS::TaskDefinition", { + ContainerDefinitions: [ + { + Environment: [ + { + Name: "SECRET_ARN", + Value: { + Ref: "secret4DA88516" + } + } + ], + Essential: true, + Image: { + "Fn::Join": [ + "", + [ + "123456789012.dkr.ecr.us-east-1.", + { + Ref: "AWS::URLSuffix" + }, + "/fake-repo:latest" + ] + ] + }, + MemoryReservation: 512, + Name: "test-container", + PortMappings: [ + { + ContainerPort: 8080, + Protocol: "tcp" + } + ] + } + ] + }); + + expect(stack).toHaveResourceLike("AWS::SecretsManager::Secret", { + Name: secretName, + }); + + expect(stack).toHaveResourceLike("AWS::EC2::VPC", { + CidrBlock: '172.168.0.0/16' + }); + + expect(stack).toHaveResourceLike("AWS::IAM::Policy", { + PolicyDocument: { + Statement: [ + { + Action: [ + "secretsmanager:GetSecretValue", + "secretsmanager:DescribeSecret" + ], + Effect: "Allow", + Resource: { + Ref: "secret4DA88516" + } + } + ] + } + }); + + expect(stack).toHaveResource("AWS::EC2::VPCEndpoint", { + ServiceName: { + "Fn::Join": [ + "", + [ + "com.amazonaws.", + { + Ref: "AWS::Region" + }, + ".secretsmanager" + ] + ] + } + }); + + // Confirm we created an Isolated VPC + expect(stack).not.toHaveResourceLike('AWS::EC2::InternetGateway', {}); + expect(stack).toCountResources('AWS::EC2::VPC', 1); + expect(stack).toCountResources('AWS::ECS::Service', 1); + expect(stack).toCountResources('AWS::SecretsManager::Secret', 1); +}); + +test('Test error invalid secret permission', () => { + const stack = new cdk.Stack(); + const publicApi = false; + + const existingVpc = defaults.getTestVpc(stack, publicApi); + + const [testService, testContainer] = defaults.CreateFargateService(stack, + 'test', + existingVpc, + undefined, + defaults.fakeEcrRepoArn, + undefined, + undefined, + undefined, + { serviceName }); + + const existingSecretObj = buildSecretsManagerSecret(stack, 'secret', {}); + + const app = () => { + new FargateToSecretsmanager(stack, 'test-construct', { + publicApi, + existingFargateServiceObject: testService, + existingContainerDefinitionObject: testContainer, + existingVpc, + grantWriteAccess: 'reed', + existingSecretObj + }); + }; + + expect(app).toThrowError('Invalid grantWriteAccess submitted - REED'); +}); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/test/integ.existing-resources.expected.json b/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/test/integ.existing-resources.expected.json new file mode 100644 index 000000000..899d61df3 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/test/integ.existing-resources.expected.json @@ -0,0 +1,1178 @@ +{ + "Description": "Integration Test with new VPC, Service and a Secret", + "Resources": { + "Vpc8378EB38": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "172.168.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "existing-resources/Vpc" + } + ] + } + }, + "VpcPublicSubnet1Subnet5C2D37C4": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1a", + "CidrBlock": "172.168.0.0/19", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "existing-resources/Vpc/PublicSubnet1" + } + ] + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W33", + "reason": "Allow Public Subnets to have MapPublicIpOnLaunch set to true" + } + ] + } + } + }, + "VpcPublicSubnet1RouteTable6C95E38E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "existing-resources/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1RouteTableAssociation97140677": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + } + } + }, + "VpcPublicSubnet1DefaultRoute3DA9E72A": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet1EIPD7E02669": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "existing-resources/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1NATGateway4D7517AA": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet1EIPD7E02669", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "existing-resources/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet2Subnet691E08A3": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1b", + "CidrBlock": "172.168.32.0/19", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "existing-resources/Vpc/PublicSubnet2" + } + ] + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W33", + "reason": "Allow Public Subnets to have MapPublicIpOnLaunch set to true" + } + ] + } + } + }, + "VpcPublicSubnet2RouteTable94F7E489": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "existing-resources/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2RouteTableAssociationDD5762D8": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + } + } + }, + "VpcPublicSubnet2DefaultRoute97F91067": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet2EIP3C605A87": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "existing-resources/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2NATGateway9182C01D": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + }, + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet2EIP3C605A87", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "existing-resources/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet3SubnetBE12F0B6": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1c", + "CidrBlock": "172.168.64.0/19", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "existing-resources/Vpc/PublicSubnet3" + } + ] + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W33", + "reason": "Allow Public Subnets to have MapPublicIpOnLaunch set to true" + } + ] + } + } + }, + "VpcPublicSubnet3RouteTable93458DBB": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "existing-resources/Vpc/PublicSubnet3" + } + ] + } + }, + "VpcPublicSubnet3RouteTableAssociation1F1EDF02": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet3RouteTable93458DBB" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet3SubnetBE12F0B6" + } + } + }, + "VpcPublicSubnet3DefaultRoute4697774F": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet3RouteTable93458DBB" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet3EIP3A666A23": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "existing-resources/Vpc/PublicSubnet3" + } + ] + } + }, + "VpcPublicSubnet3NATGateway7640CD1D": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "VpcPublicSubnet3SubnetBE12F0B6" + }, + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet3EIP3A666A23", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "existing-resources/Vpc/PublicSubnet3" + } + ] + } + }, + "VpcPrivateSubnet1Subnet536B997A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1a", + "CidrBlock": "172.168.96.0/19", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "existing-resources/Vpc/PrivateSubnet1" + } + ] + } + }, + "VpcPrivateSubnet1RouteTableB2C5B500": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "existing-resources/Vpc/PrivateSubnet1" + } + ] + } + }, + "VpcPrivateSubnet1RouteTableAssociation70C59FA6": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + } + } + }, + "VpcPrivateSubnet1DefaultRouteBE02A9ED": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet1NATGateway4D7517AA" + } + } + }, + "VpcPrivateSubnet2Subnet3788AAA1": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1b", + "CidrBlock": "172.168.128.0/19", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "existing-resources/Vpc/PrivateSubnet2" + } + ] + } + }, + "VpcPrivateSubnet2RouteTableA678073B": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "existing-resources/Vpc/PrivateSubnet2" + } + ] + } + }, + "VpcPrivateSubnet2RouteTableAssociationA89CAD56": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + } + } + }, + "VpcPrivateSubnet2DefaultRoute060D2087": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet2NATGateway9182C01D" + } + } + }, + "VpcPrivateSubnet3SubnetF258B56E": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1c", + "CidrBlock": "172.168.160.0/19", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "existing-resources/Vpc/PrivateSubnet3" + } + ] + } + }, + "VpcPrivateSubnet3RouteTableD98824C7": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "existing-resources/Vpc/PrivateSubnet3" + } + ] + } + }, + "VpcPrivateSubnet3RouteTableAssociation16BDDC43": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet3RouteTableD98824C7" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet3SubnetF258B56E" + } + } + }, + "VpcPrivateSubnet3DefaultRoute94B74F0D": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet3RouteTableD98824C7" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet3NATGateway7640CD1D" + } + } + }, + "VpcIGWD7BA715C": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "existing-resources/Vpc" + } + ] + } + }, + "VpcVPCGWBF912B6E": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "InternetGatewayId": { + "Ref": "VpcIGWD7BA715C" + } + } + }, + "VpcFlowLogIAMRole6A475D41": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "vpc-flow-logs.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Tags": [ + { + "Key": "Name", + "Value": "existing-resources/Vpc" + } + ] + } + }, + "VpcFlowLogIAMRoleDefaultPolicy406FB995": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents", + "logs:DescribeLogStreams" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "VpcFlowLogLogGroup7B5C56B9", + "Arn" + ] + } + }, + { + "Action": "iam:PassRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "VpcFlowLogIAMRole6A475D41", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "VpcFlowLogIAMRoleDefaultPolicy406FB995", + "Roles": [ + { + "Ref": "VpcFlowLogIAMRole6A475D41" + } + ] + } + }, + "VpcFlowLogLogGroup7B5C56B9": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "RetentionInDays": 731, + "Tags": [ + { + "Key": "Name", + "Value": "existing-resources/Vpc" + } + ] + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain", + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W84", + "reason": "By default CloudWatchLogs LogGroups data is encrypted using the CloudWatch server-side encryption keys (AWS Managed Keys)" + } + ] + } + } + }, + "VpcFlowLog8FF33A73": { + "Type": "AWS::EC2::FlowLog", + "Properties": { + "ResourceId": { + "Ref": "Vpc8378EB38" + }, + "ResourceType": "VPC", + "TrafficType": "ALL", + "DeliverLogsPermissionArn": { + "Fn::GetAtt": [ + "VpcFlowLogIAMRole6A475D41", + "Arn" + ] + }, + "LogDestinationType": "cloud-watch-logs", + "LogGroupName": { + "Ref": "VpcFlowLogLogGroup7B5C56B9" + }, + "Tags": [ + { + "Key": "Name", + "Value": "existing-resources/Vpc" + } + ] + } + }, + "VpcECRAPI9A3B6A2B": { + "Type": "AWS::EC2::VPCEndpoint", + "Properties": { + "ServiceName": "com.amazonaws.us-east-1.ecr.api", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "PrivateDnsEnabled": true, + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "existingresourcesECRAPIsecuritygroup78294485", + "GroupId" + ] + } + ], + "SubnetIds": [ + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + }, + { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + }, + { + "Ref": "VpcPrivateSubnet3SubnetF258B56E" + } + ], + "VpcEndpointType": "Interface" + } + }, + "VpcECRDKR604E039F": { + "Type": "AWS::EC2::VPCEndpoint", + "Properties": { + "ServiceName": "com.amazonaws.us-east-1.ecr.dkr", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "PrivateDnsEnabled": true, + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "existingresourcesECRDKRsecuritygroup598BA37E", + "GroupId" + ] + } + ], + "SubnetIds": [ + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + }, + { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + }, + { + "Ref": "VpcPrivateSubnet3SubnetF258B56E" + } + ], + "VpcEndpointType": "Interface" + } + }, + "VpcS3A5408339": { + "Type": "AWS::EC2::VPCEndpoint", + "Properties": { + "ServiceName": { + "Fn::Join": [ + "", + [ + "com.amazonaws.", + { + "Ref": "AWS::Region" + }, + ".s3" + ] + ] + }, + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "RouteTableIds": [ + { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + { + "Ref": "VpcPrivateSubnet3RouteTableD98824C7" + }, + { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + { + "Ref": "VpcPublicSubnet3RouteTable93458DBB" + } + ], + "VpcEndpointType": "Gateway" + } + }, + "VpcSECRETSMANAGERF52907C2": { + "Type": "AWS::EC2::VPCEndpoint", + "Properties": { + "ServiceName": "com.amazonaws.us-east-1.secretsmanager", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "PrivateDnsEnabled": true, + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "existingresourcesSECRETSMANAGERsecuritygroup8010FC5B", + "GroupId" + ] + } + ], + "SubnetIds": [ + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + }, + { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + }, + { + "Ref": "VpcPrivateSubnet3SubnetF258B56E" + } + ], + "VpcEndpointType": "Interface" + } + }, + "secret4DA88516": { + "Type": "AWS::SecretsManager::Secret", + "Properties": { + "GenerateSecretString": {} + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain", + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W77", + "reason": "We allow the use of the AWS account default key aws/secretsmanager for secret encryption." + } + ] + } + } + }, + "existingresourcesECRAPIsecuritygroup78294485": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "existing-resources/existing-resources-ECR_API-security-group", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "SecurityGroupIngress": [ + { + "CidrIp": { + "Fn::GetAtt": [ + "Vpc8378EB38", + "CidrBlock" + ] + }, + "Description": { + "Fn::Join": [ + "", + [ + "from ", + { + "Fn::GetAtt": [ + "Vpc8378EB38", + "CidrBlock" + ] + }, + ":443" + ] + ] + }, + "FromPort": 443, + "IpProtocol": "tcp", + "ToPort": 443 + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W5", + "reason": "Egress of 0.0.0.0/0 is default and generally considered OK" + }, + { + "id": "W40", + "reason": "Egress IPProtocol of -1 is default and generally considered OK" + } + ] + } + } + }, + "existingresourcesECRDKRsecuritygroup598BA37E": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "existing-resources/existing-resources-ECR_DKR-security-group", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "SecurityGroupIngress": [ + { + "CidrIp": { + "Fn::GetAtt": [ + "Vpc8378EB38", + "CidrBlock" + ] + }, + "Description": { + "Fn::Join": [ + "", + [ + "from ", + { + "Fn::GetAtt": [ + "Vpc8378EB38", + "CidrBlock" + ] + }, + ":443" + ] + ] + }, + "FromPort": 443, + "IpProtocol": "tcp", + "ToPort": 443 + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W5", + "reason": "Egress of 0.0.0.0/0 is default and generally considered OK" + }, + { + "id": "W40", + "reason": "Egress IPProtocol of -1 is default and generally considered OK" + } + ] + } + } + }, + "testclusterDF8B0D19": { + "Type": "AWS::ECS::Cluster" + }, + "testtaskdefTaskRoleB2DEF113": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "testtaskdefTaskRoleDefaultPolicy5D591D1C": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "secretsmanager:GetSecretValue", + "secretsmanager:DescribeSecret" + ], + "Effect": "Allow", + "Resource": { + "Ref": "secret4DA88516" + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "testtaskdefTaskRoleDefaultPolicy5D591D1C", + "Roles": [ + { + "Ref": "testtaskdefTaskRoleB2DEF113" + } + ] + } + }, + "testtaskdefF924AD58": { + "Type": "AWS::ECS::TaskDefinition", + "Properties": { + "ContainerDefinitions": [ + { + "Environment": [ + { + "Name": "SECRET_ARN", + "Value": { + "Ref": "secret4DA88516" + } + } + ], + "Essential": true, + "Image": "nginx", + "MemoryReservation": 512, + "Name": "test-container", + "PortMappings": [ + { + "ContainerPort": 8080, + "Protocol": "tcp" + } + ] + } + ], + "Cpu": "256", + "Family": "existingresourcestesttaskdef88B214A2", + "Memory": "512", + "NetworkMode": "awsvpc", + "RequiresCompatibilities": [ + "FARGATE" + ], + "TaskRoleArn": { + "Fn::GetAtt": [ + "testtaskdefTaskRoleB2DEF113", + "Arn" + ] + } + } + }, + "testsg872EB48A": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "Construct created security group", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W5", + "reason": "Egress of 0.0.0.0/0 is default and generally considered OK" + }, + { + "id": "W40", + "reason": "Egress IPProtocol of -1 is default and generally considered OK" + } + ] + } + } + }, + "testserviceService2730C249": { + "Type": "AWS::ECS::Service", + "Properties": { + "Cluster": { + "Ref": "testclusterDF8B0D19" + }, + "DeploymentConfiguration": { + "MaximumPercent": 150, + "MinimumHealthyPercent": 75 + }, + "DesiredCount": 2, + "EnableECSManagedTags": false, + "LaunchType": "FARGATE", + "NetworkConfiguration": { + "AwsvpcConfiguration": { + "AssignPublicIp": "DISABLED", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "testsg872EB48A", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + }, + { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + }, + { + "Ref": "VpcPrivateSubnet3SubnetF258B56E" + } + ] + } + }, + "PlatformVersion": "LATEST", + "TaskDefinition": { + "Ref": "testtaskdefF924AD58" + } + } + }, + "existingresourcesSECRETSMANAGERsecuritygroup8010FC5B": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "existing-resources/existing-resources-SECRETS_MANAGER-security-group", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "SecurityGroupIngress": [ + { + "CidrIp": { + "Fn::GetAtt": [ + "Vpc8378EB38", + "CidrBlock" + ] + }, + "Description": { + "Fn::Join": [ + "", + [ + "from ", + { + "Fn::GetAtt": [ + "Vpc8378EB38", + "CidrBlock" + ] + }, + ":443" + ] + ] + }, + "FromPort": 443, + "IpProtocol": "tcp", + "ToPort": 443 + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W5", + "reason": "Egress of 0.0.0.0/0 is default and generally considered OK" + }, + { + "id": "W40", + "reason": "Egress IPProtocol of -1 is default and generally considered OK" + } + ] + } + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/test/integ.existing-resources.ts b/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/test/integ.existing-resources.ts new file mode 100644 index 000000000..27aea17fd --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/test/integ.existing-resources.ts @@ -0,0 +1,54 @@ +/** + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +// Imports +import { Aws, App, Stack } from "@aws-cdk/core"; +import { FargateToSecretsmanager, FargateToSecretsmanagerProps } from "../lib"; +import { generateIntegStackName, getTestVpc, CreateFargateService } from '@aws-solutions-constructs/core'; +import * as ecs from '@aws-cdk/aws-ecs'; +import * as defaults from '@aws-solutions-constructs/core'; + +// Setup +const app = new App(); +const stack = new Stack(app, generateIntegStackName(__filename), { + env: { account: Aws.ACCOUNT_ID, region: 'us-east-1' }, +}); +stack.templateOptions.description = 'Integration Test with new VPC, Service and a Secret'; + +const existingVpc = getTestVpc(stack); +const existingSecretObj = defaults.buildSecretsManagerSecret(stack, 'secret', {}); + +const image = ecs.ContainerImage.fromRegistry('nginx'); + +const [testService, testContainer] = CreateFargateService(stack, + 'test', + existingVpc, + undefined, + undefined, + undefined, + undefined, + { image }, +); + +const constructProps: FargateToSecretsmanagerProps = { + publicApi: true, + existingVpc, + existingSecretObj, + existingContainerDefinitionObject: testContainer, + existingFargateServiceObject: testService, +}; + +new FargateToSecretsmanager(stack, 'test-construct', constructProps); + +// Synth +app.synth(); \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/test/integ.new-resources.expected.json b/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/test/integ.new-resources.expected.json new file mode 100644 index 000000000..d1eeaa495 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/test/integ.new-resources.expected.json @@ -0,0 +1,1188 @@ +{ + "Description": "Integration Test with new VPC, Service and a Secret", + "Resources": { + "testconstructsecret1A43460A": { + "Type": "AWS::SecretsManager::Secret", + "Properties": { + "GenerateSecretString": {} + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain", + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W77", + "reason": "We allow the use of the AWS account default key aws/secretsmanager for secret encryption." + } + ] + } + } + }, + "Vpc8378EB38": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "new-resources/Vpc" + } + ] + } + }, + "VpcPublicSubnet1Subnet5C2D37C4": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1a", + "CidrBlock": "10.0.0.0/19", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "new-resources/Vpc/PublicSubnet1" + } + ] + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W33", + "reason": "Allow Public Subnets to have MapPublicIpOnLaunch set to true" + } + ] + } + } + }, + "VpcPublicSubnet1RouteTable6C95E38E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "new-resources/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1RouteTableAssociation97140677": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + } + } + }, + "VpcPublicSubnet1DefaultRoute3DA9E72A": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet1EIPD7E02669": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "new-resources/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1NATGateway4D7517AA": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet1EIPD7E02669", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "new-resources/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet2Subnet691E08A3": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1b", + "CidrBlock": "10.0.32.0/19", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "new-resources/Vpc/PublicSubnet2" + } + ] + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W33", + "reason": "Allow Public Subnets to have MapPublicIpOnLaunch set to true" + } + ] + } + } + }, + "VpcPublicSubnet2RouteTable94F7E489": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "new-resources/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2RouteTableAssociationDD5762D8": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + } + } + }, + "VpcPublicSubnet2DefaultRoute97F91067": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet2EIP3C605A87": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "new-resources/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2NATGateway9182C01D": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + }, + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet2EIP3C605A87", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "new-resources/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet3SubnetBE12F0B6": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1c", + "CidrBlock": "10.0.64.0/19", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "new-resources/Vpc/PublicSubnet3" + } + ] + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W33", + "reason": "Allow Public Subnets to have MapPublicIpOnLaunch set to true" + } + ] + } + } + }, + "VpcPublicSubnet3RouteTable93458DBB": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "new-resources/Vpc/PublicSubnet3" + } + ] + } + }, + "VpcPublicSubnet3RouteTableAssociation1F1EDF02": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet3RouteTable93458DBB" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet3SubnetBE12F0B6" + } + } + }, + "VpcPublicSubnet3DefaultRoute4697774F": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet3RouteTable93458DBB" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet3EIP3A666A23": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "new-resources/Vpc/PublicSubnet3" + } + ] + } + }, + "VpcPublicSubnet3NATGateway7640CD1D": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "SubnetId": { + "Ref": "VpcPublicSubnet3SubnetBE12F0B6" + }, + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet3EIP3A666A23", + "AllocationId" + ] + }, + "Tags": [ + { + "Key": "Name", + "Value": "new-resources/Vpc/PublicSubnet3" + } + ] + } + }, + "VpcPrivateSubnet1Subnet536B997A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1a", + "CidrBlock": "10.0.96.0/19", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "new-resources/Vpc/PrivateSubnet1" + } + ] + } + }, + "VpcPrivateSubnet1RouteTableB2C5B500": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "new-resources/Vpc/PrivateSubnet1" + } + ] + } + }, + "VpcPrivateSubnet1RouteTableAssociation70C59FA6": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + } + } + }, + "VpcPrivateSubnet1DefaultRouteBE02A9ED": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet1NATGateway4D7517AA" + } + } + }, + "VpcPrivateSubnet2Subnet3788AAA1": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1b", + "CidrBlock": "10.0.128.0/19", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "new-resources/Vpc/PrivateSubnet2" + } + ] + } + }, + "VpcPrivateSubnet2RouteTableA678073B": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "new-resources/Vpc/PrivateSubnet2" + } + ] + } + }, + "VpcPrivateSubnet2RouteTableAssociationA89CAD56": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + } + } + }, + "VpcPrivateSubnet2DefaultRoute060D2087": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet2NATGateway9182C01D" + } + } + }, + "VpcPrivateSubnet3SubnetF258B56E": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1c", + "CidrBlock": "10.0.160.0/19", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "new-resources/Vpc/PrivateSubnet3" + } + ] + } + }, + "VpcPrivateSubnet3RouteTableD98824C7": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "new-resources/Vpc/PrivateSubnet3" + } + ] + } + }, + "VpcPrivateSubnet3RouteTableAssociation16BDDC43": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet3RouteTableD98824C7" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet3SubnetF258B56E" + } + } + }, + "VpcPrivateSubnet3DefaultRoute94B74F0D": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet3RouteTableD98824C7" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet3NATGateway7640CD1D" + } + } + }, + "VpcIGWD7BA715C": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "new-resources/Vpc" + } + ] + } + }, + "VpcVPCGWBF912B6E": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "InternetGatewayId": { + "Ref": "VpcIGWD7BA715C" + } + } + }, + "VpcFlowLogIAMRole6A475D41": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "vpc-flow-logs.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Tags": [ + { + "Key": "Name", + "Value": "new-resources/Vpc" + } + ] + } + }, + "VpcFlowLogIAMRoleDefaultPolicy406FB995": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents", + "logs:DescribeLogStreams" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "VpcFlowLogLogGroup7B5C56B9", + "Arn" + ] + } + }, + { + "Action": "iam:PassRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "VpcFlowLogIAMRole6A475D41", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "VpcFlowLogIAMRoleDefaultPolicy406FB995", + "Roles": [ + { + "Ref": "VpcFlowLogIAMRole6A475D41" + } + ] + } + }, + "VpcFlowLogLogGroup7B5C56B9": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "RetentionInDays": 731, + "Tags": [ + { + "Key": "Name", + "Value": "new-resources/Vpc" + } + ] + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain", + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W84", + "reason": "By default CloudWatchLogs LogGroups data is encrypted using the CloudWatch server-side encryption keys (AWS Managed Keys)" + } + ] + } + } + }, + "VpcFlowLog8FF33A73": { + "Type": "AWS::EC2::FlowLog", + "Properties": { + "ResourceId": { + "Ref": "Vpc8378EB38" + }, + "ResourceType": "VPC", + "TrafficType": "ALL", + "DeliverLogsPermissionArn": { + "Fn::GetAtt": [ + "VpcFlowLogIAMRole6A475D41", + "Arn" + ] + }, + "LogDestinationType": "cloud-watch-logs", + "LogGroupName": { + "Ref": "VpcFlowLogLogGroup7B5C56B9" + }, + "Tags": [ + { + "Key": "Name", + "Value": "new-resources/Vpc" + } + ] + } + }, + "VpcSECRETSMANAGERF52907C2": { + "Type": "AWS::EC2::VPCEndpoint", + "Properties": { + "ServiceName": "com.amazonaws.us-east-1.secretsmanager", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "PrivateDnsEnabled": true, + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "newresourcesSECRETSMANAGERsecuritygroupD22DA6BC", + "GroupId" + ] + } + ], + "SubnetIds": [ + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + }, + { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + }, + { + "Ref": "VpcPrivateSubnet3SubnetF258B56E" + } + ], + "VpcEndpointType": "Interface" + } + }, + "VpcECRAPI9A3B6A2B": { + "Type": "AWS::EC2::VPCEndpoint", + "Properties": { + "ServiceName": "com.amazonaws.us-east-1.ecr.api", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "PrivateDnsEnabled": true, + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "newresourcesECRAPIsecuritygroupE52BAE3F", + "GroupId" + ] + } + ], + "SubnetIds": [ + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + }, + { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + }, + { + "Ref": "VpcPrivateSubnet3SubnetF258B56E" + } + ], + "VpcEndpointType": "Interface" + } + }, + "VpcECRDKR604E039F": { + "Type": "AWS::EC2::VPCEndpoint", + "Properties": { + "ServiceName": "com.amazonaws.us-east-1.ecr.dkr", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "PrivateDnsEnabled": true, + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "newresourcesECRDKRsecuritygroupBA34F94F", + "GroupId" + ] + } + ], + "SubnetIds": [ + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + }, + { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + }, + { + "Ref": "VpcPrivateSubnet3SubnetF258B56E" + } + ], + "VpcEndpointType": "Interface" + } + }, + "VpcS3A5408339": { + "Type": "AWS::EC2::VPCEndpoint", + "Properties": { + "ServiceName": { + "Fn::Join": [ + "", + [ + "com.amazonaws.", + { + "Ref": "AWS::Region" + }, + ".s3" + ] + ] + }, + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "RouteTableIds": [ + { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + { + "Ref": "VpcPrivateSubnet3RouteTableD98824C7" + }, + { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + { + "Ref": "VpcPublicSubnet3RouteTable93458DBB" + } + ], + "VpcEndpointType": "Gateway" + } + }, + "newresourcesSECRETSMANAGERsecuritygroupD22DA6BC": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "new-resources/new-resources-SECRETS_MANAGER-security-group", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "SecurityGroupIngress": [ + { + "CidrIp": { + "Fn::GetAtt": [ + "Vpc8378EB38", + "CidrBlock" + ] + }, + "Description": { + "Fn::Join": [ + "", + [ + "from ", + { + "Fn::GetAtt": [ + "Vpc8378EB38", + "CidrBlock" + ] + }, + ":443" + ] + ] + }, + "FromPort": 443, + "IpProtocol": "tcp", + "ToPort": 443 + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W5", + "reason": "Egress of 0.0.0.0/0 is default and generally considered OK" + }, + { + "id": "W40", + "reason": "Egress IPProtocol of -1 is default and generally considered OK" + } + ] + } + } + }, + "newresourcesECRAPIsecuritygroupE52BAE3F": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "new-resources/new-resources-ECR_API-security-group", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "SecurityGroupIngress": [ + { + "CidrIp": { + "Fn::GetAtt": [ + "Vpc8378EB38", + "CidrBlock" + ] + }, + "Description": { + "Fn::Join": [ + "", + [ + "from ", + { + "Fn::GetAtt": [ + "Vpc8378EB38", + "CidrBlock" + ] + }, + ":443" + ] + ] + }, + "FromPort": 443, + "IpProtocol": "tcp", + "ToPort": 443 + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W5", + "reason": "Egress of 0.0.0.0/0 is default and generally considered OK" + }, + { + "id": "W40", + "reason": "Egress IPProtocol of -1 is default and generally considered OK" + } + ] + } + } + }, + "newresourcesECRDKRsecuritygroupBA34F94F": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "new-resources/new-resources-ECR_DKR-security-group", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "SecurityGroupIngress": [ + { + "CidrIp": { + "Fn::GetAtt": [ + "Vpc8378EB38", + "CidrBlock" + ] + }, + "Description": { + "Fn::Join": [ + "", + [ + "from ", + { + "Fn::GetAtt": [ + "Vpc8378EB38", + "CidrBlock" + ] + }, + ":443" + ] + ] + }, + "FromPort": 443, + "IpProtocol": "tcp", + "ToPort": 443 + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W5", + "reason": "Egress of 0.0.0.0/0 is default and generally considered OK" + }, + { + "id": "W40", + "reason": "Egress IPProtocol of -1 is default and generally considered OK" + } + ] + } + } + }, + "testconstructcluster7B6231C5": { + "Type": "AWS::ECS::Cluster" + }, + "testconstructtaskdefTaskRoleC60414C4": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "testconstructtaskdefTaskRoleDefaultPolicyF34A1535": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "secretsmanager:GetSecretValue", + "secretsmanager:DescribeSecret" + ], + "Effect": "Allow", + "Resource": { + "Ref": "testconstructsecret1A43460A" + } + }, + { + "Action": [ + "secretsmanager:PutSecretValue", + "secretsmanager:UpdateSecret" + ], + "Effect": "Allow", + "Resource": { + "Ref": "testconstructsecret1A43460A" + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "testconstructtaskdefTaskRoleDefaultPolicyF34A1535", + "Roles": [ + { + "Ref": "testconstructtaskdefTaskRoleC60414C4" + } + ] + } + }, + "testconstructtaskdef8BD1F9E4": { + "Type": "AWS::ECS::TaskDefinition", + "Properties": { + "ContainerDefinitions": [ + { + "Environment": [ + { + "Name": "SECRET_ARN", + "Value": { + "Ref": "testconstructsecret1A43460A" + } + } + ], + "Essential": true, + "Image": "nginx", + "MemoryReservation": 512, + "Name": "test-construct-container", + "PortMappings": [ + { + "ContainerPort": 8080, + "Protocol": "tcp" + } + ] + } + ], + "Cpu": "256", + "Family": "newresourcestestconstructtaskdefE4616A0D", + "Memory": "512", + "NetworkMode": "awsvpc", + "RequiresCompatibilities": [ + "FARGATE" + ], + "TaskRoleArn": { + "Fn::GetAtt": [ + "testconstructtaskdefTaskRoleC60414C4", + "Arn" + ] + } + } + }, + "testconstructsgA602AA29": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "Construct created security group", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + }, + "Metadata": { + "cfn_nag": { + "rules_to_suppress": [ + { + "id": "W5", + "reason": "Egress of 0.0.0.0/0 is default and generally considered OK" + }, + { + "id": "W40", + "reason": "Egress IPProtocol of -1 is default and generally considered OK" + } + ] + } + } + }, + "testconstructserviceService13074A8F": { + "Type": "AWS::ECS::Service", + "Properties": { + "Cluster": { + "Ref": "testconstructcluster7B6231C5" + }, + "DeploymentConfiguration": { + "MaximumPercent": 150, + "MinimumHealthyPercent": 75 + }, + "DesiredCount": 2, + "EnableECSManagedTags": false, + "LaunchType": "FARGATE", + "NetworkConfiguration": { + "AwsvpcConfiguration": { + "AssignPublicIp": "DISABLED", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "testconstructsgA602AA29", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + }, + { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + }, + { + "Ref": "VpcPrivateSubnet3SubnetF258B56E" + } + ] + } + }, + "PlatformVersion": "LATEST", + "TaskDefinition": { + "Ref": "testconstructtaskdef8BD1F9E4" + } + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/test/integ.new-resources.ts b/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/test/integ.new-resources.ts new file mode 100644 index 000000000..383020581 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/test/integ.new-resources.ts @@ -0,0 +1,40 @@ +/** + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ + +// Imports +import { Aws, App, Stack } from "@aws-cdk/core"; +import { FargateToSecretsmanager, FargateToSecretsmanagerProps } from "../lib"; +import { generateIntegStackName } from '@aws-solutions-constructs/core'; +import * as ecs from '@aws-cdk/aws-ecs'; + +// Setup +const app = new App(); +const stack = new Stack(app, generateIntegStackName(__filename), { + env: { account: Aws.ACCOUNT_ID, region: 'us-east-1' }, +}); +stack.templateOptions.description = 'Integration Test with new VPC, Service and a Secret'; + +const image = ecs.ContainerImage.fromRegistry('nginx'); + +const testProps: FargateToSecretsmanagerProps = { + publicApi: true, + containerDefinitionProps: { + image + }, + grantWriteAccess: 'readwrite' +}; + +new FargateToSecretsmanager(stack, 'test-construct', testProps); + +// Synth +app.synth(); From 20c4a004fd888de19373109411a505ba00e8c19e Mon Sep 17 00:00:00 2001 From: mickychetta Date: Tue, 3 May 2022 19:24:57 +0000 Subject: [PATCH 3/3] fixed doc typo and created cidr constant --- .../aws-fargate-secretsmanager/README.md | 3 +++ .../aws-fargate-secretsmanager/lib/index.ts | 4 ++-- .../test/fargate-secretsmanager.test.ts | 7 ++++--- .../aws-lambda-secretsmanager/README.md | 1 - 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/README.md b/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/README.md index 2e295be39..7b0707c86 100644 --- a/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/README.md +++ b/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/README.md @@ -111,7 +111,10 @@ Out of the box implementation of the Construct without any override will set the ### Amazon Secrets Manager Secret * Sets up an Amazon Secrets Manager secret * Uses an existing secret if one is provided, otherwise creates a new one + * (default) random name + * (default) random value * Adds an Interface Endpoint to the VPC for Secrets Manager (the service by default runs in Isolated or Private subnets) +* Retain the Secret when deleting the CloudFormation stack ## Architecture ![Architecture Diagram](architecture.png) diff --git a/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/lib/index.ts b/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/lib/index.ts index 3bbbdb79d..15b828614 100644 --- a/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/lib/index.ts +++ b/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/lib/index.ts @@ -30,7 +30,7 @@ export interface FargateToSecretsmanagerProps { * Optional custom properties for a VPC the construct will create. This VPC will * be used by the new Fargate service the construct creates (that's * why targetGroupProps can't include a VPC). Providing - * both this and existingVpc is an error. An Secrets Manager Interface + * both this and existingVpc is an error. A Secrets Manager Interface * endpoint will be included in this VPC. * * @default - none @@ -39,7 +39,7 @@ export interface FargateToSecretsmanagerProps { /** * An existing VPC in which to deploy the construct. Providing both this and * vpcProps is an error. If the client provides an existing Fargate service, - * this value must be the VPC where the service is running. An Secrets Manager Interface + * this value must be the VPC where the service is running. A Secrets Manager Interface * endpoint will be added to this VPC. * * @default - none diff --git a/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/test/fargate-secretsmanager.test.ts b/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/test/fargate-secretsmanager.test.ts index 7b94f6908..8424f2405 100644 --- a/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/test/fargate-secretsmanager.test.ts +++ b/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/test/fargate-secretsmanager.test.ts @@ -24,6 +24,7 @@ const serviceName = "custom-service-name"; const familyName = "family-name"; const secretName = 'custom-name'; const envName = 'CUSTOM_SECRET_ARN'; +const cidr = '172.0.0.0/16'; test('New service/new secret, public API, new VPC', () => { const stack = new cdk.Stack(); @@ -32,7 +33,7 @@ test('New service/new secret, public API, new VPC', () => { const construct = new FargateToSecretsmanager(stack, 'test-construct', { publicApi, ecrRepositoryArn: defaults.fakeEcrRepoArn, - vpcProps: { cidr: '172.0.0.0/16' }, + vpcProps: { cidr }, clusterProps: { clusterName }, containerDefinitionProps: { containerName }, fargateTaskDefinitionProps: { family: familyName }, @@ -113,7 +114,7 @@ test('New service/new secret, public API, new VPC', () => { }); expect(stack).toHaveResourceLike("AWS::EC2::VPC", { - CidrBlock: '172.0.0.0/16' + CidrBlock: cidr }); expect(stack).toHaveResource("AWS::EC2::VPCEndpoint", { @@ -145,7 +146,7 @@ test('New service/new secret, private API, new VPC', () => { new FargateToSecretsmanager(stack, 'test-construct', { publicApi, ecrRepositoryArn: defaults.fakeEcrRepoArn, - vpcProps: { cidr: '172.0.0.0/16' }, + vpcProps: { cidr }, secretProps: { secretName }, diff --git a/source/patterns/@aws-solutions-constructs/aws-lambda-secretsmanager/README.md b/source/patterns/@aws-solutions-constructs/aws-lambda-secretsmanager/README.md index 77d71853e..bacb326c1 100644 --- a/source/patterns/@aws-solutions-constructs/aws-lambda-secretsmanager/README.md +++ b/source/patterns/@aws-solutions-constructs/aws-lambda-secretsmanager/README.md @@ -118,7 +118,6 @@ Out of the box implementation of the Construct without any override will set the ### Amazon SecretsManager Secret * Enable read-only access for the associated AWS Lambda Function -* Enable server-side encryption using a default KMS key for the account and region * Creates a new Secret * (default) random name * (default) random value