From cd218b6900a174afa09c86f28fb0650ecfe37942 Mon Sep 17 00:00:00 2001 From: mickychetta <45010053+mickychetta@users.noreply.github.com> Date: Wed, 4 May 2022 04:25:55 -0700 Subject: [PATCH] feat(aws-fargate-secretsmanager): Create new construct (#670) * created README.md * created new construct * fixed doc typo and created cidr constant --- .../aws-fargate-secretsmanager/.eslintignore | 4 + .../aws-fargate-secretsmanager/.gitignore | 15 + .../aws-fargate-secretsmanager/.npmignore | 21 + .../aws-fargate-secretsmanager/README.md | 123 ++ .../architecture.png | Bin 0 -> 127509 bytes .../aws-fargate-secretsmanager/lib/index.ts | 197 +++ .../aws-fargate-secretsmanager/package.json | 104 ++ .../test/fargate-secretsmanager.test.ts | 640 +++++++++ .../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 + .../aws-lambda-secretsmanager/README.md | 1 - 13 files changed, 3564 insertions(+), 1 deletion(-) 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/README.md create mode 100644 source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/architecture.png 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/README.md b/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/README.md new file mode 100644 index 000000000..7b0707c86 --- /dev/null +++ b/source/patterns/@aws-solutions-constructs/aws-fargate-secretsmanager/README.md @@ -0,0 +1,123 @@ +# 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 + * (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) + +*** +© 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@7o&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$wSseN36zj Uw4&rPf54C($C^qV4*66Av)GCu6`UO zrp7$lMkL#4+uAD(zU>C@>^yFiF+4XUy1j RB+LE#7Be|*5 zKZSVru?{MnNFveww^^N=n37|W@`9T%uoUZB*OWg> zfV8_VFxL58_LT}(p;Ok{Mb+}fvn)(z@fU3&p0o4NrG=a7WE#>jpXjboD=?9ljyZ?_ zk 0) 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?yP3 mKPF%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_5 DP@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`K4iFmRH edDw}?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_41JQBv c#2HBr(o-Qphl6*8ahWt1-acR6x$+xBuOD2be O7Xl-hZbg p8xxR6kwZjcA_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>oqB!`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$IMNdQVz 8ukHOrs&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#EIb16 Y z5&g_-F;B5I>j{jjw${x26OQ4%3u5YQkM*b8AOYqpb}@)pCZOl8CshKS2$0;C;#6;j zRiDLskCpaJMI>NGGZ^3YrVR;Z;OKOHOee wd}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`q Ps=s3a&CMMdyZ|Qq9UlV0$$b=#W zl&|wpnyFxR+>}evKYyH`{);F`99X#I#qyn0FH87!k+5OsuUx+&qgb&Uv(=wkLys7V zDyr_zFQO@!_?ZKyOlBFfU~UCJTRtjxR(u G{i4xB8qCkP8767Z z(6{_4A&z)S>g@k5tiN#r8JgH49w)vfY)OW1 RR2G=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*BK3 jlxm>%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+Y UdBHpmw-3^8aku+;TOwh28xj7%@bG_YyPj2sBY```#hg2+=!l1PbDhjFu(3q zp{0Ox2LzTKVgz6+^qxSwd*0m^_*e)S8B3CW*Y1}Z-e}{om0g!pPGPGJ9oN_erUBP4 z^yg-KRDUemV*(W?>X GOMijTG?}+dLGTw@pq{K#^Swf6iWv ao_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-%BiHn DqZ-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 _#fpv5a k3hhK8q1SnY+hs>`;86Gk;ljY4X_<>jKor=Y0I~6Y) zBu(jwgR`ltux5}8XzCJAs2E4mmnc~WLuy+?1#LSowR);AYX^Vd`w-I^S%-g?;7IBl z0+b|DP24 j?B zDPu tN z(R=vpBO++vtKg_xf&_G{W?smRUb`q{s~J$_1?5%i -&X9eplMCE>|8!vH6uog`eAt zTYCX61lh;o2Hk;|u zvb=&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~S
K?{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)KfNi6 p5 zm96GIfMWq@d{A+gP{ff`LO+MQeECee2og*`24Do eqgLWdV9$+|LNU@htvM= zgY0BO6z+l& 9J;Xe z1L=bUFsy&CJ-&VUIG$?%&vIM9GCP#`Vx6YOFH A-iN`05znSO* 4 g&A!2Jb@+-z#=>z`olcLdl9HK}f@c|%v+qxk@a783zzUyH z1~Wk Vo*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{3 8+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%|MJC6 OI-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*`Kj z%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{6YAE4ro vjK95w zgC;4kOV5Q;4%7dtyLjK)vGV>_;@;!{&$29tRw_wkNiZ;p^`Xay&ctW%uKK0t9js<# zr11dH{6tuZWNg6RWva1nwAOWbiHm&TugE`E6bjA~C|Q;~r2DrefLO_9K13 JZKoF*ho+Mcou9Tkf`zS@cH7}7@7Gz~V8X QpD0%Dj>RR_SRMR9E1{ n$6~23Duj+hr@7J~XbVc?iLY8KS@)8Z8a{^OC z{)m+HWB?bGO3GC@fgE*i#MuqUs$?ZU+j(UGMn)%mL s*f+>2KZu=E ?f`7SpN^92XtvF3}7X9 z1K|@n*I1Xzof(VCQlY*-0T7&~@}lIGg#}76=o{I T1Xx 3Kbhqbw`#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{ii OreTpEAco|A%35|QHsXS*m4mV&c=d6|Eu8zVUh zjP;y+D`ZYp?fgwp2fdW;ca$OFyqP QY9X4+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*$R B2n)s!I(O4!F>+BILO52>rr6$dsAuP{`=V`^DXMRI-OQmlk27qMHEI;j57m%sA zLs!A!H#>09j>@Y;!*8di7uDwYy<8slO^l pv7? 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^i