From 46d3e245274e6f9475d726c8345f6427a853fb98 Mon Sep 17 00:00:00 2001 From: bertrand <bpinel@ippon.fr> Date: Thu, 31 Jan 2019 22:50:22 +0100 Subject: [PATCH] Add S3 bucket and CloudFront terraform --- cloud/cloudformation/api.yaml | 388 ---------------------------------- cloud/terraform/README.md | 6 +- cloud/terraform/bucket.tf | 27 +++ cloud/terraform/cloudfront.tf | 123 +++++++++++ cloud/xmind/API Gateway.png | Bin 0 -> 14817 bytes 5 files changed, 153 insertions(+), 391 deletions(-) delete mode 100644 cloud/cloudformation/api.yaml create mode 100644 cloud/terraform/bucket.tf create mode 100644 cloud/terraform/cloudfront.tf create mode 100644 cloud/xmind/API Gateway.png diff --git a/cloud/cloudformation/api.yaml b/cloud/cloudformation/api.yaml deleted file mode 100644 index 03973b9..0000000 --- a/cloud/cloudformation/api.yaml +++ /dev/null @@ -1,388 +0,0 @@ -AWSTemplateFormatVersion: '2010-09-09' -Transform: AWS::Serverless-2016-10-31 -Description: Ember EHipster API -Parameters: - LambdaFunctionName: - Type: String - Default: "EmberEHipsterFunction" - Description: "Name of the lambda function" - AppClientName: - Type: String - Default: "EmberEHipsterWebClient" - Description: "Cognito user pools app client name" -Resources: - ApiGatewayApi: - Type: AWS::Serverless::Api - DependsOn: CognitoUserPool - Properties: - StageName: Prod - DefinitionBody: - swagger: "2.0" - info: - version: "2017-02-24T04:09:00Z" - title: "EmberEHipsterAPI" - basePath: "/Prod" - schemes: - - "https" - paths: - "/{type}": - get: - consumes: - - "application/json" - produces: - - "application/json" - parameters: - - name: "user" - in: "header" - required: false - type: "string" - - name: "id" - in: "query" - required: false - type: "string" - responses: - "200": - description: "200 response" - schema: - $ref: "#/definitions/Empty" - headers: - Access-Control-Allow-Origin: - type: "string" - security: - - EmberEHipster: [] - x-amazon-apigateway-integration: - responses: - default: - statusCode: "200" - responseParameters: - method.response.header.Access-Control-Allow-Origin: "'*'" - requestTemplates: - application/json: "## See http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html\n\ - ## This template will pass through all parameters including path, querystring,\ - \ header, stage variables, and context through to the integration endpoint\ - \ via the body/payload\n#set($allParams = $input.params())\n{\n\"body-json\"\ - \ : $input.json('$'),\n\"params\" : {\n#foreach($type in $allParams.keySet())\n\ - \ #set($params = $allParams.get($type))\n\"$type\" : {\n #foreach($paramName\ - \ in $params.keySet())\n \"$paramName\" : \"$util.escapeJavaScript($params.get($paramName))\"\ - \n #if($foreach.hasNext),#end\n #end\n}\n #if($foreach.hasNext),#end\n\ - #end\n},\n\"stage-variables\" : {\n#foreach($key in $stageVariables.keySet())\n\ - \"$key\" : \"$util.escapeJavaScript($stageVariables.get($key))\"\n \ - \ #if($foreach.hasNext),#end\n#end\n},\n\"context\" : {\n \"user\"\ - \ : \"$context.authorizer.claims.sub\",\n\t\"email\" : \"$context.authorizer.claims.email\"\ - ,\n \"account-id\" : \"$context.identity.accountId\",\n \"api-id\"\ - \ : \"$context.apiId\",\n \"api-key\" : \"$context.identity.apiKey\"\ - ,\n \"authorizer-principal-id\" : \"$context.authorizer.principalId\"\ - ,\n \"caller\" : \"$context.identity.caller\",\n \"cognito-authentication-provider\"\ - \ : \"$context.identity.cognitoAuthenticationProvider\",\n \"cognito-authentication-type\"\ - \ : \"$context.identity.cognitoAuthenticationType\",\n \"cognito-identity-id\"\ - \ : \"$context.identity.cognitoIdentityId\",\n \"cognito-identity-pool-id\"\ - \ : \"$context.identity.cognitoIdentityPoolId\",\n \"httpMethod\" :\ - \ \"$context.httpMethod\",\n \"stage\" : \"$context.stage\",\n \"\ - source-ip\" : \"$context.identity.sourceIp\",\n \"user\" : \"$context.identity.user\"\ - ,\n \"user-agent\" : \"$context.identity.userAgent\",\n \"user-arn\"\ - \ : \"$context.identity.userArn\",\n \"request-id\" : \"$context.requestId\"\ - ,\n \"resource-id\" : \"$context.resourceId\",\n \"resource-path\"\ - \ : \"$context.resourcePath\"\n }\n}\n" - uri: !Join [ "", [ "arn:aws:apigateway:", !Ref "AWS::Region", ":lambda:path/2015-03-31/functions/arn:aws:lambda:", !Ref "AWS::Region", ":", !Ref "AWS::AccountId", ":function:", !Ref "LambdaFunctionName", "/invocations" ] ] - passthroughBehavior: "when_no_templates" - httpMethod: "POST" - contentHandling: "CONVERT_TO_TEXT" - type: "aws" - post: - consumes: - - "application/json" - produces: - - "application/json" - parameters: - - name: "user" - in: "header" - required: false - type: "string" - responses: - "200": - description: "200 response" - schema: - $ref: "#/definitions/Empty" - headers: - Access-Control-Allow-Origin: - type: "string" - security: - - EmberEHipster: [] - x-amazon-apigateway-integration: - responses: - default: - statusCode: "200" - responseParameters: - method.response.header.Access-Control-Allow-Origin: "'*'" - requestTemplates: - application/json: "## See http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html\n\ - ## This template will pass through all parameters including path, querystring,\ - \ header, stage variables, and context through to the integration endpoint\ - \ via the body/payload\n#set($allParams = $input.params())\n{\n\"body-json\"\ - \ : $input.json('$'),\n\"params\" : {\n#foreach($type in $allParams.keySet())\n\ - \ #set($params = $allParams.get($type))\n\"$type\" : {\n #foreach($paramName\ - \ in $params.keySet())\n \"$paramName\" : \"$util.escapeJavaScript($params.get($paramName))\"\ - \n #if($foreach.hasNext),#end\n #end\n}\n #if($foreach.hasNext),#end\n\ - #end\n},\n\"stage-variables\" : {\n#foreach($key in $stageVariables.keySet())\n\ - \"$key\" : \"$util.escapeJavaScript($stageVariables.get($key))\"\n \ - \ #if($foreach.hasNext),#end\n#end\n},\n\"context\" : {\n \"user\"\ - \ : \"$context.authorizer.claims.sub\",\n\t\"email\" : \"$context.authorizer.claims.email\"\ - ,\n \"account-id\" : \"$context.identity.accountId\",\n \"api-id\"\ - \ : \"$context.apiId\",\n \"api-key\" : \"$context.identity.apiKey\"\ - ,\n \"authorizer-principal-id\" : \"$context.authorizer.principalId\"\ - ,\n \"caller\" : \"$context.identity.caller\",\n \"cognito-authentication-provider\"\ - \ : \"$context.identity.cognitoAuthenticationProvider\",\n \"cognito-authentication-type\"\ - \ : \"$context.identity.cognitoAuthenticationType\",\n \"cognito-identity-id\"\ - \ : \"$context.identity.cognitoIdentityId\",\n \"cognito-identity-pool-id\"\ - \ : \"$context.identity.cognitoIdentityPoolId\",\n \"httpMethod\" :\ - \ \"$context.httpMethod\",\n \"stage\" : \"$context.stage\",\n \"\ - source-ip\" : \"$context.identity.sourceIp\",\n \"user\" : \"$context.identity.user\"\ - ,\n \"user-agent\" : \"$context.identity.userAgent\",\n \"user-arn\"\ - \ : \"$context.identity.userArn\",\n \"request-id\" : \"$context.requestId\"\ - ,\n \"resource-id\" : \"$context.resourceId\",\n \"resource-path\"\ - \ : \"$context.resourcePath\"\n }\n}\n" - uri: !Join [ "", [ "arn:aws:apigateway:", !Ref "AWS::Region", ":lambda:path/2015-03-31/functions/arn:aws:lambda:", !Ref "AWS::Region", ":", !Ref "AWS::AccountId", ":function:", !Ref "LambdaFunctionName", "/invocations" ] ] - passthroughBehavior: "when_no_templates" - httpMethod: "POST" - contentHandling: "CONVERT_TO_TEXT" - type: "aws" - delete: - consumes: - - "application/json" - produces: - - "application/json" - parameters: - - name: "user" - in: "header" - required: false - type: "string" - responses: - "200": - description: "200 response" - schema: - $ref: "#/definitions/Empty" - headers: - Access-Control-Allow-Origin: - type: "string" - security: - - EmberEHipster: [] - x-amazon-apigateway-integration: - responses: - default: - statusCode: "200" - responseParameters: - method.response.header.Access-Control-Allow-Origin: "'*'" - requestTemplates: - application/json: "## See http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html\n\ - ## This template will pass through all parameters including path, querystring,\ - \ header, stage variables, and context through to the integration endpoint\ - \ via the body/payload\n#set($allParams = $input.params())\n{\n\"body-json\"\ - \ : $input.json('$'),\n\"params\" : {\n#foreach($type in $allParams.keySet())\n\ - \ #set($params = $allParams.get($type))\n\"$type\" : {\n #foreach($paramName\ - \ in $params.keySet())\n \"$paramName\" : \"$util.escapeJavaScript($params.get($paramName))\"\ - \n #if($foreach.hasNext),#end\n #end\n}\n #if($foreach.hasNext),#end\n\ - #end\n},\n\"stage-variables\" : {\n#foreach($key in $stageVariables.keySet())\n\ - \"$key\" : \"$util.escapeJavaScript($stageVariables.get($key))\"\n \ - \ #if($foreach.hasNext),#end\n#end\n},\n\"context\" : {\n \"user\"\ - \ : \"$context.authorizer.claims.sub\",\n\t\"email\" : \"$context.authorizer.claims.email\"\ - ,\n \"account-id\" : \"$context.identity.accountId\",\n \"api-id\"\ - \ : \"$context.apiId\",\n \"api-key\" : \"$context.identity.apiKey\"\ - ,\n \"authorizer-principal-id\" : \"$context.authorizer.principalId\"\ - ,\n \"caller\" : \"$context.identity.caller\",\n \"cognito-authentication-provider\"\ - \ : \"$context.identity.cognitoAuthenticationProvider\",\n \"cognito-authentication-type\"\ - \ : \"$context.identity.cognitoAuthenticationType\",\n \"cognito-identity-id\"\ - \ : \"$context.identity.cognitoIdentityId\",\n \"cognito-identity-pool-id\"\ - \ : \"$context.identity.cognitoIdentityPoolId\",\n \"httpMethod\" :\ - \ \"$context.httpMethod\",\n \"stage\" : \"$context.stage\",\n \"\ - source-ip\" : \"$context.identity.sourceIp\",\n \"user\" : \"$context.identity.user\"\ - ,\n \"user-agent\" : \"$context.identity.userAgent\",\n \"user-arn\"\ - \ : \"$context.identity.userArn\",\n \"request-id\" : \"$context.requestId\"\ - ,\n \"resource-id\" : \"$context.resourceId\",\n \"resource-path\"\ - \ : \"$context.resourcePath\"\n }\n}\n" - uri: !Join [ "", [ "arn:aws:apigateway:", !Ref "AWS::Region", ":lambda:path/2015-03-31/functions/arn:aws:lambda:", !Ref "AWS::Region", ":", !Ref "AWS::AccountId", ":function:", !Ref "LambdaFunctionName", "/invocations" ] ] - passthroughBehavior: "when_no_templates" - httpMethod: "POST" - contentHandling: "CONVERT_TO_TEXT" - type: "aws" - options: - consumes: - - "application/json" - produces: - - "application/json" - responses: - "200": - description: "200 response" - schema: - $ref: "#/definitions/Empty" - headers: - Access-Control-Allow-Origin: - type: "string" - Access-Control-Allow-Methods: - type: "string" - Access-Control-Allow-Headers: - type: "string" - x-amazon-apigateway-integration: - responses: - default: - statusCode: "200" - responseParameters: - method.response.header.Access-Control-Allow-Methods: "'GET,OPTIONS,POST,PUT,DELETE'" - method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,user'" - method.response.header.Access-Control-Allow-Origin: "'*'" - requestTemplates: - application/json: "{\"statusCode\": 200}" - passthroughBehavior: "when_no_match" - type: "mock" - securityDefinitions: - EmberEHipster: - type: "apiKey" - name: "user" - in: "header" - x-amazon-apigateway-authtype: "cognito_user_pools" - x-amazon-apigateway-authorizer: - providerARNs: - - !GetAtt CognitoUserPool.Arn - type: "cognito_user_pools" - definitions: - Empty: - type: "object" - title: "Empty Schema" - LambdaFunction: - Type: AWS::Serverless::Function - Properties: - FunctionName: !Ref LambdaFunctionName - Handler: index.handler - Runtime: nodejs4.3 - Policies: AmazonDynamoDBFullAccess - Environment: - Variables: - TABLE_NAME: !Ref Table - Events: - GetDoc: - Type: Api - Properties: - RestApiId: !Ref ApiGatewayApi - Path: /{type}/{id} - Method: GET - GetDocs: - Type: Api - Properties: - RestApiId: !Ref ApiGatewayApi - Path: /{type} - Method: GET - NewDoc: - Type: Api - Properties: - RestApiId: !Ref ApiGatewayApi - Path: /{type} - Method: POST - DeleteDoc: - Type: Api - Properties: - RestApiId: !Ref ApiGatewayApi - Path: /{type}/{id} - Method: DELETE - CognitoUserPool: - Type: AWS::Cognito::UserPool - Properties: - UserPoolName: EmberEHipster - AutoVerifiedAttributes: - - "email" - CognitoUserPoolClient: - Type: AWS::Cognito::UserPoolClient - DependsOn: CognitoUserPool - Properties: - ClientName: !Ref AppClientName - UserPoolId: !Ref CognitoUserPool - GenerateSecret: false - CognitoIdentityPool: - Type: AWS::Cognito::IdentityPool - Properties: - AllowUnauthenticatedIdentities: true - CognitoIdentityProviders: - - ClientId: !Ref CognitoUserPoolClient - ProviderName: !GetAtt CognitoUserPool.ProviderName - CognitoIdentityPoolRoles: - Type: AWS::Cognito::IdentityPoolRoleAttachment - DependsOn: CognitoIdentityPool - Properties: - IdentityPoolId: !Ref CognitoIdentityPool - Roles: - authenticated: !GetAtt AuthenticatedRole.Arn - unauthenticated: !GetAtt UnauthenticatedRole.Arn - UnauthenticatedRole: - Type: AWS::IAM::Role - Properties: - AssumeRolePolicyDocument: - Version: '2012-10-17' - Statement: - - Effect: Allow - Principal: - Federated: cognito-identity.amazonaws.com - Action: sts:AssumeRoleWithWebIdentity - Condition: - StringEquals: - cognito-identity.amazonaws.com:aud: !Ref CognitoIdentityPool - ForAnyValue:StringLike: - cognito-identity.amazonaws.com:amr: unauthenticated - Policies: - - - PolicyName: EmberEHipsterUnauthenticatedApi - PolicyDocument: - Version: '2012-10-17' - Statement: - - Effect: Allow - Action: - - mobileanalytics:PutEvents - - cognito-sync:* - Resource: - - "*" - AuthenticatedRole: - Type: AWS::IAM::Role - Properties: - AssumeRolePolicyDocument: - Version: '2012-10-17' - Statement: - - Effect: Allow - Principal: - Federated: cognito-identity.amazonaws.com - Action: sts:AssumeRoleWithWebIdentity - Condition: - StringEquals: - cognito-identity.amazonaws.com:aud: !Ref CognitoIdentityPool - ForAnyValue:StringLike: - cognito-identity.amazonaws.com:amr: authenticated - Policies: - - - PolicyName: EmberEHipsterAuthenticatedApi - PolicyDocument: - Version: '2012-10-17' - Statement: - - Effect: Allow - Action: - - execute-api:Invoke - Resource: !Join [ "", [ "arn:aws:execute-api:", !Ref "AWS::Region", ":", !Ref "AWS::AccountId", ":", !Ref ApiGatewayApi, "/*" ] ] - Table: - Type: AWS::Serverless::SimpleTable -Outputs: - CognitoIdentityPoolId: - Description: Cognito Identity Pool ID - Value: !Ref CognitoIdentityPool - CognitoUserPoolsId: - Description: Cognito User Pools ID - Value: !Ref CognitoUserPool - CognitoUserPoolsClientId: - Description: Cognito User Pools App Client ID - Value: !Ref CognitoUserPoolClient - Api: - Description: API Gateway ID - Value: !Ref ApiGatewayApi - ApiUrl: - Description: URL of your API endpoint - Value: !Join - - '' - - - https:// - - !Ref ApiGatewayApi - - '.execute-api.' - - !Ref 'AWS::Region' - - '.amazonaws.com/Prod' diff --git a/cloud/terraform/README.md b/cloud/terraform/README.md index 743f9d8..f965987 100644 --- a/cloud/terraform/README.md +++ b/cloud/terraform/README.md @@ -4,7 +4,7 @@ Installing AWS infrastructure through Terraform scripts This file explains how to set up the expected infrastructure awaited to implement the JSON API. The installation is based on Terraform (https://www.terraform.io/) -It expects that by AWS CLI and Terraform have previously been installed and set up correctly. +It expects that both AWS CLI and Terraform have been previously installed and set up correctly. The AWS Credentials should also have been set up How it works @@ -14,7 +14,7 @@ AWS JSON API server relies on 3 main components, plus the needed IAM roles : - A lambda function in charge of the translation from JSON API format to dynamoDB objects and managing the relationships and the optional parameters provided in the request - An API Gateway configured to received the REST HTTP requests and proxying the Lambda -Three Terraform files are available, each one created the needed component and their relationships with each other. +Several Terraform files are available, each one creating the needed component and their relationships with each other. Installation ------------------------------------------------------------------------------ @@ -31,6 +31,6 @@ aws s3 cp lambda-jsonapi.zip s3://lambda-jsonapi-code-bucket/v1.0.0/lambda-jsona ``` One this is done, simply run the terraform script : ``` -terraform apply +terraform apply -var bucket_name=<bucket name for static web site> ``` For a strange reason I don't understand, the first time you run the terraform command, the mapping for the integration response fails. You have to run the command again to have your complete setup. \ No newline at end of file diff --git a/cloud/terraform/bucket.tf b/cloud/terraform/bucket.tf new file mode 100644 index 0000000..dfd49d2 --- /dev/null +++ b/cloud/terraform/bucket.tf @@ -0,0 +1,27 @@ +variable "bucket_name_production" { + type = "string" + default = "ember-aws-ehipster-production" +} +variable "bucket_name_staging" { + type = "string" + default = "ember-aws-ehipster-staging" +} + +resource "aws_s3_bucket" "production" { + bucket = "${var.bucket_name_production}" + acl = "public-read" + + website { + index_document = "index.html" + error_document = "index.html" + } +} +resource "aws_s3_bucket" "staging" { + bucket = "${var.bucket_name_staging}" + acl = "public-read" + + website { + index_document = "index.html" + error_document = "index.html" + } +} diff --git a/cloud/terraform/cloudfront.tf b/cloud/terraform/cloudfront.tf new file mode 100644 index 0000000..b3378b7 --- /dev/null +++ b/cloud/terraform/cloudfront.tf @@ -0,0 +1,123 @@ + +locals { + s3_origin_id = "S3Origin" +} + +data "aws_s3_bucket" "production" { + bucket = "${var.bucket_name_production}" +} + +data "aws_s3_bucket" "staging" { + bucket = "${var.bucket_name_staging}" +} +resource "aws_cloudfront_distribution" "s3_distribution_production" { + origin { + domain_name = "${element(split("/","${data.aws_s3_bucket.production.website_endpoint}"),2)}" + origin_id = "${local.s3_origin_id}" + + custom_origin_config { + http_port = 80 + https_port = 443 + origin_protocol_policy = "http-only" + origin_ssl_protocols = ["SSLv3", "TLSv1.1", "TLSv1.2"] + } + } + + enabled = true + http_version = "http2" + is_ipv6_enabled = true + comment = "Production ehipster ClondFront" + default_root_object = "index.html" + + default_cache_behavior { + allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"] + compress = true + cached_methods = ["GET", "HEAD"] + target_origin_id = "${local.s3_origin_id}" + + forwarded_values { + query_string = true + + cookies { + forward = "none" + } + } + + viewer_protocol_policy = "redirect-to-https" + min_ttl = 0 + default_ttl = 3600 + max_ttl = 86400 + } + + price_class = "PriceClass_All" + + restrictions { + geo_restriction { + restriction_type = "none" + } + } + + tags = { + Environment = "production" + } + + viewer_certificate { + cloudfront_default_certificate = true + } +} + +resource "aws_cloudfront_distribution" "s3_distribution_staging" { + origin { + domain_name = "${element(split("/","${data.aws_s3_bucket.staging.website_endpoint}"),2)}" + origin_id = "${local.s3_origin_id}" + + custom_origin_config { + http_port = 80 + https_port = 443 + origin_protocol_policy = "http-only" + origin_ssl_protocols = ["SSLv3", "TLSv1.1", "TLSv1.2"] + } + } + + enabled = true + http_version = "http2" + is_ipv6_enabled = true + comment = "Staging ehipster ClondFront" + default_root_object = "index.html" + + default_cache_behavior { + allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"] + compress = true + cached_methods = ["GET", "HEAD"] + target_origin_id = "${local.s3_origin_id}" + + forwarded_values { + query_string = true + + cookies { + forward = "none" + } + } + + viewer_protocol_policy = "redirect-to-https" + min_ttl = 0 + default_ttl = 3600 + max_ttl = 86400 + } + + price_class = "PriceClass_All" + + restrictions { + geo_restriction { + restriction_type = "none" + } + } + + tags = { + Environment = "staging" + } + + viewer_certificate { + cloudfront_default_certificate = true + } +} \ No newline at end of file diff --git a/cloud/xmind/API Gateway.png b/cloud/xmind/API Gateway.png new file mode 100644 index 0000000000000000000000000000000000000000..a836be8091d7192d5741fb585df8767ae68e4c61 GIT binary patch literal 14817 zcmc(GWmuGL*X{@?A!VQrAR$Ug=m1iZLrJGdE8WP@jrcHhcZqa^AR%qg-QC^YxvxRr z=Z){%$Nu(@ee6HZ$lP<^*R|HV);iB~t?`kS7RAFM#(_W}c(25S<RK7r4Dfy(3l01m z$n``U0^!VjB_yC|AHSY}`R4B6$tBylrcP{g^>#xXLZQG2EAytfg|COV#t#n<L5zAd zkLx<Zw{FrurX31ME_^G@FN7g?OHD|~KUr~*BtV1zxOU^1yS90wXpM>C>zecA;_jN& zLOhE>M;ksjxqe3*YuM|%s7FJ*BlSHFQEwpp{1kB1D<NAjCF<3ixQP(;Dukztg?dfC z0YfbrB8Lt|ebYVj1MW5O%HPM2ME#k<`2XG4KZ}krj_geRl;kvzAiGti1FVNyOcxc| zyj0HdE=S_Z>MB`0@v-Qu$SD_e$OC$?19!4*4!4RbTp|k!a>paJE%GO#*Eh)^-ziYb z-ou)QDOM#d9LOCJLij~dI~cKAQ*3C^z!nY6C~VG+->Wuq;*kFussJO{vhrRXWX?2{ zl|z0CKUZ~OyihrbA&~MQxCS961vcU*X%T0}$Hh4tOB;3n%>T6DN93=UbS#gUPKXo~ zFv*z@zjsTgh&cDv7Y3wM?-KBexHy%ReobqOT+kmbhtkLD!S-k^V3<;`@3p0!8K`4b zCUUE9*<eB*JV0&Z1qL(jCJU2$9Io2E=G%F>3@kFF$wI=%X;psZX-|&B1O0=R7Z%~z zFS5|mV0B*fST!5KJm@I%+#o5S>?WDHyZZ|Hk~)AOn#ILf5hJR(SvG-IllmhtgEh*v zpYawxVusAf&eSbSvIJeL>rdX5kHJ0ejNxU`-<~NcRM(Tz^LEo0+hDi_uI`$;GS(Cn zmL%#<{>0ah`LV%cw$qE#pRER|Rjs<ts3>r`@(vQ+>#%mX`7G0vbuS`$@~)mYjBj<L zjeZseh43e$+)%9BJTf$3TWaQ1;W}2|%*~KmmHXYm(a?%2<#64U&l+*`{YPM(cedW5 zzHG_8+de(dMG#YMgpkkMsBf=a=ceqFMJ*?Q0JRU%DRAT8AMIh)-!EwqAi~&u4Eg@u z9p!QFQRTAn);Jqx3tPsvWE>B>+mN?!QD6R_g|2%h9rIHx%?B3&m7}fH)a^Bw6W<A) zchMlR7{E()tpUD2?HCwG4y>ibCq-XBws!+3gAmxlJ8H^vn!^R0n5)amHq1|Z53wik zz&X1IV&_p>Rqm9qj*j|6bO;3ef9CpN6Zj?$6pg<PwYdNNyFO855u)cXeVr1YbV5rt zv~zwjzWGG&@ZQOsGH+~Lq0MBeUzD^Q*OOgHT*&On{!I@H9Ef`~FdT*E3Kjg8Wp_bz zEZ32Jxj)%{Ugc=r^{;fjZXJ6v36<ZjA9ZJJ`qQz~<H%e_p4)VimHzU=#~~Aq;j_@} zZRnmj#w1Uvsa72wuD}d5H!_b*h@(782MGg;+CbW;sAg3a75z^fLO#34G+9}mIc}u~ z#6Kf!cXwk*NJ<c`Bb^o8lfm<;x@1Ga-(nI(eEoxxaKfh&m4J~1fUm&%nBkaTLvP~9 zV7A3DLHj0Ibbro0*)8umwrtfncWJh%S?NxAQ}aAO`RulGOEbt)Sok0Sw&mTR#*`sl z29w)5_z$JhI=6QX5NUi&T1%|Pe<>bVi^7s*Ds9e-yjshvW^?bNP6+vxhmaML4ZyYF zNr`dAhhz)fR>txtH6K-)Sykj*vrY-9K0aD3l1H6%x2xq5zeXZD$;`S7br2%A(_Xo2 zva__dyh<b{nufwYD7_~j<*`N(O2C2pPSCH`MGoDGAsZQMS~{KBIBQcHeNkm$aB4w= zd7>Wj1&NT3?^t`O+eM>M=?o5m?NQg{jukBnH+xD+g4s=<$Byv$DH4!rQA@r5kjJ`k zYX@2$9~$cXQW6Ax8Hf@>W_vU?2t<q$T!Hez|L9$4(oT^Fh|S4wmDQ%&f?<w+Q=>SL zx8JYs_ZYhQMzwf-IX1i|C|7Lj0;GG2s}DGqfs;_C&_+rcx}EU;s01tfi3Y+oH9U&w z%X7HLAt(}mU58<~+y7vLfv6V*>tg}2|L^bIGmrGn@5;$7-awsXci;>m&na6psIf08 zcSND3^3RZ2BU9K@8W)QZvcSIFx$rjhGtwDU6x)$NKJx}sQar*ayVE4wo1D8h^ZoPp zPK^pYES%yUVpGnxFc{TLvet7po2_jG0qHucah<3J=_tnt_vMsMUzxHAw_1zo@2It3 z1X%=>vqv6Z|04c~-y{paRrRc@^Bcq*W~%a7qPVE2?WLvnZKC<89j1lI$T<Zr7;k!9 zBEgfP$B@t1*T5IiV2?3%#n@hG!jswS=oZ3o9VCg#pBbF=WnwXfg;7HI!$2k$!q64N zZwfYU!z6fApH%158>3H#?wupNm<EIHB(k{Zx6Ra>=#;krE5$OQwnhO*@FNvx|1HOM zFVI&xi?mcqHekXq_<Mnc|8ZMz{x9`uc2kPFyOe!h6~ZZ7&T=bRa&Vz&pO=AWey=f8 zIx;uF%*<f4G-I_<BfjdFU`s2GbGp|Hc9`R2#{rzii-2@Z%s*4r*8GN&31tZFLmhBF ztSI38Lj1@%cib#&Zb}TuGsVCE@W*S{QCs^Ra{P){SN@+q5O&d}<)WOvW3YH<v6KB< zB`q?gz~XXhN<K@ma+nhC3*rcryvv61b-g=xR83s?x6IHWQ{dp;N-Du+oUDoo)O4Yi zt~%VBp3TlqE-K<a+%z$uU#TnY>x*1i(E3*Kn$ID*eob6&$#kMQpt2NGxI?X$WxB82 zG?Fe9zVqKC>XRF~Sl(E9dP|kOE}F3C4o64N(+KA0g5hmGe!YSUeFT?V?^VTH3UyT5 zp3=bCEz~uOT=7a4=2l6FiG&k3Rn>&+L?wi&;6HBu5-tLXaQ)9>OIX=2E0mcd2UL-t zjOFi^>0mPI)t8#ZGNp+KAnz4u^|rKRZ;h0+ELvt|W=2HJdweqtq5b*K65&3928BtM z(wJ$L&EBW*8i4KjW@YVr-wKbuMFP?X4FapjuV8G<e6o7=-UBi0WViRvlpZ{|{gaw5 zk^bx6N}Z4dbHN~&^-+;%5L>0~wpyUH*wN4ah>^>DnZ$=iM(VEGj$Dj-!|77{;YnW| zXePgwQlDm>nFN#g=+22dsQmD2X!yo;10s7(x9QpO&yvciS@=HTy6)1C6jA1469YcP zW2?J84~>22sI8af=~c^|4s+H=d#7y`<Zmkt#fR?|Wzh*7dm#{GjR87-8ylOKzsAZf zRNk<%!$zWFnpFee?5?f!i)vIldU{q5m$Re=#dFpB9*}VghFQ(du2R8Q?aX8E-(Tf0 zR!l2;M!rUQ0~+ePU`zqwH=%$dvBQ$2;V<Jw0wAO#&0PgqS;fyA8Q*-TCSnfTkKqVG z6m`TW?PZ-gi&q9hfBD*`rgf)I#&ep&IJx7fW0cP~JNdNOSx&tI@JM{a!=f4+48O+e zv0L<{_HRuq_4li`2B!o?eGIZQRJ@xdbI_c;)IW?Xt`OMnJ?*yIC9vgEatk;aqKFP0 z6pj0k^Aokb%pMM-uAzvjDL^kT0t1!B#f`&~Tw-FvS@bWQ#bs1#;(OE-<d0x?6N0}} z=On%~q713(&bpJP5JYZZ5IwLl(Qcmy_u4$!n~ufPs0gP&YyB||EIrapn74zhV=PI4 z79Aek_Icv1%XST-ih3{wT)bNlsh{^oE(14Y#(8Q<Y+z?@xfg`QMCliMMU^Z?B&}&x zHQi#z`RR5MWe>u6ZLO^zSN)4SB6I$vsVmE>vqN}WROV~b)2gnP7Wv1K6rueX5JNoR z?+>_i!j-MMqq<Se4Wc3g)bP8Ma&qCvy^)N2oQMGiCM`K+%|`#Kr|mkKveG)QbF_v) zrOhHf=Hbz!4Oq;*AmY%<I8y;<RtEw%+<nj1V^B|oycf)2dCDxmv--^U>2rWz&ytH% z=s?=iSq3RSm$-3%^*$}%qT~Q`W!c;(o))%<$CX*@J@YKNs;&b0QzV!W3Qt@p!1rl= zZV)!%ljg8uvy98VJGCA4$&Xr`Qxs?}C=8{MKNEvKGDV1&_wp%ANW_sqUFH`Wl=8Fl z7d8qlg0J<B`Gy@<ndlj9^1P%)d>Z$D*SF)raf`=5Y_&gi{Qw-Yk3mtbOLCJHl~U@@ z;^wu!+3yJJ%*H(T8`dQeby_5M_CGl=HZxcy<ETh0AMPH*orEg*pYD^9a&@=qxEnNK z3@Bdu=w!Jr_`v&!&qLBlIVp}BuA#~@5=U2zZojtV(@dw+q2bQxL^b=9CqxkumNr}j z55-5%dN;<8SzyqUN}(dd^OHO}x_iHn+e8LS#YU{03=9m<dCnK})GkNAE@mfitH{B} zRXZ(L)DJ2i*7_&%h4I!OV62?v>OXxdfw(Uo-|0sEV=#YS&QWA9ne3V?0NF)cYpHU& z&XVkW<S$;Wz;Wu?X4tKWYw5HF>vC`P3Svxxf_>wU^G%?}vr9*wIZxR+Up(?~_AY(D zR#1zGS&MjxFz}DY<vcm5F?%QA6D28GWzc4?u2e6+$>+?07|2x~Ez3y0KT@)~H2tIc z)L+9=g5&+?B(Uw^BreL2oY4{Lvi4kWJFJ^OUoh;pk}-)|u>jjGB#un$dM~kcBywBS zSs0J>Tz^h7>yz&aN<Cx`vnlu;8dTnQ0LD7gNDllNF<#X?e`dKTZ7F=cwj(%8ArVih z#x)_aIRNTNU&)kiO>3=WvmX(9+x8>ReRQ~>ChLpzG{cd8;wIUT`P5L!n?8T>o(7Uz zEg90!uW=V2_L^$|TD6nF*>AG)IG<VzsV))}Tcum0t)Tc$c8g<%#Eu;lIe_MV9}E*H zSA5*5Z`QI~h0W>tF^}HaH>=Bw$T?j^l=;O4LOAEnonf2jM4L0R{@WoTtjd$0DWp!D z(O`Y<InA}D^;^-b7l|(h$n7j2O_VOFt2=*~-_<iTJ;H5$_i2ow^+j&@3}7;jxPLz_ zxrxiF!KUp&`5iunilgK10oTrk1%%~uzQk(Nh;FB!82eE~&nzaYlyc}3wWNAkDd2eD zVi7Rwk|k#etFKlLjl-$si{FR-Vq<8JN}A0GDh?LI*MFjL(srz~siGzhaO2YRV9H+@ zu+;FAreNQpsF-p_w*5knO7#lcby;cr*|Ygpyh>T3Yf$6N{=L7Uta_lD`y?n5*iL2Q zdgy7FMmpyNCOH7~f3;!(>>v~(Y~iiLrffFa)zEzbJe_F0F8B#JS7v0xE{|biWz6Vn z?n8+C6h9L8PE)YF^<((Sy<p07mG;)!L?Y+?YY-b${R3I<18y?_TM!7HZj&rXOxIzL zy~G#<9_L^~x`=;=1~^vuA#XgyUf+Ry2KohRD|efMNgowrgyd5HDHx~_q~sLR#(tzC z1#utv^HhMfyz6covbDD2c-%+2&2AhN`CuPzWu$vdkq|;YV#0Kz`H|s2RXoHptv-6s z@k`_!2c`*+e#(EfeA?uTu}J{opLEBCQiGI)Wlj@JDe(Q62;vUJ5F|EupvNfqm(Y;y zFk|yh6$u1Vef{cw?p)piNC3cwqA7nkp+UOTt}e&Q{P;ju?BzeVg|<R*TY|9J7!6W< z;~(FvyDd}{3xVW)xO(Pi{D0pfxCOAxYJXeHtI4=hFR^xUCVDRBsAi(V_{S4?CcZZE zS9(TqN*2NfV^S94eecSCXe$t?yp)jo-qZASd|piyVE(1dFmC_|o-Hw($Bv3L$Y(?b zb1TJ`#da4z8Z#{l0BsK+?ipjCMdYhL{K0+~lKhXY@grAKBO>%b@(ZVZT76_xmOmP5 zyT$Ufg?V11ZGMrqqiH4lU$JzmHun|rL)q{%KFAW&YSM|8*Z4%3wPVP_8UP36Wuq4? zB#xp|7HtWmB(k|8{HT+7&Bh(KAA1tv7(@_VYJIJB?{6?olw-+>ut{yv6)VKQ(ggl_ zbB09`;ptP`Qb1;@lpN$Fg##61aOnoFL0Cbq0fB?jcMYaU)25t%FCno?_d+d!o9OPR z$eqUv;@uXBHg*e(xngg=QZT6+Jv3T})fJ#a41wkWoQ;)<4wwLMFC65jY6}C20qvgJ z&WU24vo?YRQr`DpQXaW>#3i&fww=or;<ra9#C4B!lzIx?uBMbW;`Xqhg$UieI`F7X zU14b5OnxK?l>c-%J;S@<vk55=$9(E5rUyz2>d`=>OAV%k176UVBn{2(o<-e-80T;I z{(XF|*$@WdM*J(tjy0{os_BHh4Zb=OgtD01O<mlP_dvm*PTQn<dfWLcI%E&;&z&g9 zpg%c5gyZ}pWbJjYh+x?Z2*1TYf>JveuuHTT`eIvoc>f;V`yiMB-TS>a)^E7NG>@HU z)|_X~j(zzqzU&`p7Q0rfdtwHW4IdxLhO9<EXK3smEOUGRK1&gZA`q<We{5v<2C7=) zz`{0UFjMD5K(1MkX`iByQF!YX!iGL4d!az{ye8pcL{Yq1Ru*G>d+mFhJlCDnPcZ8j zn{y2sqkz!oO~$-mQMXOVp*x!W8b|7}SqNFe`m@KaiWn7vX`?h@)ut{xmy;@8?VYXd zSuPXtmpq)%<Shb%jB&R4q33`xECjSl3=|}Z#5=pdQ<9f<HB556j|^YqKs|7;eg2)B z&}`&2Uw66k*Gf5+(lYi7B2{DK+{SaFxY*`(Y0Vq)M>Ld8N7Dhd(X366h)|j?F^TA$ zm6wxy`n2`BWv8=P39eDGY_~{Cb624Pl*I1I`&tbZ&Sj_GgZHWWPcn4LrWOeY3$zH& z3b0vi{`llq{>MrqK2B>^r9!cra`yFoeU5cl;-;^pFIw6Iu>5@92}dX?)qFLmHPC^# z%&=H39J-wD=+QpsNvJ3^?WJDF?=;wzt}!r^I#4(sq+n0hmq}<v;`Z|qgQWG}OP#R? zPydffr^hAThjjek{$o?AaGiG=h}2Y|VI2Lk!)!>4LqNvkn54md8bU>vaCo|zqNiNb z<n)FvhEOUN_rVl7*@e-e0XK~n5=9U=0YNC?cqwY(9e2EoXp~E#kMOM1nTjsw6GX0% z2T-7>*s1@`De7O5!bL*Bl5KaqXyxQw={B{^jb8DlQO)X993RViNTJEf{IOYQcbJ!f z5OeY@A-R<B^kARj$b18ePZUH(f=ar3u#RF<L&Gt6G|9~a2M5kj`bvi|dNn+9O(l)I z<>~T4j?4U-6v32aF_$8ld%^`B`m(VIQqhyM#`4vh4GrSTq_9+1Ru9}^dpyb#r8XMq zPBbg;+m5{~xm)VgG;jzj*6-}I;v<s$jOs;cd59c|Xyel-PZ+#_8Wu#Ju?JvI`3XV? zNh=t|l*V<5cyGV86q4?>i07kb%*~>^xn5#>DiROPb^}j|As`d-h5OveU}=AOgHYM8 zLqZQW$7gWT7G3W**no(jgnxe5{RWBQX2fE+f>qOtZqp@p7chw=LFB-O>kF6<-(xX3 zB-(w3&s^jjT*j1eQ$<3zJyyS}pEs>Z>!Xuy_=p5kb_Qefpq<gmsXJ40QY{bLIUf6m z+r{_;F_x7fn6hi=_k9)bmNqkm1qAu)hB-q=LgOWk%%9l=c;H$_HM2VQ5ngUpQSip% z$DBRyyU(;(t7CNQLuE5DEeN%L*ELMiDd6G9Ds~31p)d?$pq(0;VoWZ7;OaAg*tBcu zE$!9&wd>~p@&$CnWu)2(j`w+iOLAg2KO(LAaO-oEgDn+O?&DgGBb{z0WwNDto>tL| zQXsH_LJn})4*pFb?{57@HZI(wzcV(~^JHV7&@1M73ME_n--4(@plAUBQ`|hVUMFL! z%+)rl0E{nJmTn*L+HR1}TW@WBZgzLreN97cyS<yTXY!D1jo10;;+GfOeoe`z*}n2M z8*W!9pBuGH_9cdKz&+JpPV?UBEFgD*#7B+v^fcnKfD2PqouyuUxg!W+1@-VBMijg| z!olS_y&~>IG+Jt@#qNMUf2Jv~4SNe|eNGOm>uB^!i=jU+*1MT@2cNX7d%C=|TH4QM zt`;Gb0;K~20k_k9%V2w34Z=!6b=p!%Nls-r;q;KrOy%70uc$;w*WIiF+~p<ad+)>m z#`86##G!IMD+D1p+#Xk=^liJ5oZVvbbsPDE+dIjH+vIi|hQ%CgdP~SP<yBA>@rnXh zY7wcWM_(kOMs$||V6?H3R*xSD$hlMDdW;Q`y46G|xcKMBURG8>n>&{&Gq2^V&5%rZ zKn{K7Bn%KTp50R$z09gy_mRnvs;>eAa?4LeKN<I+X(8wI`&5yN*8GKHgUo!_KGUH= z1(#k@z@=`jH}vWzaL#h+OHS{vu5{l1gMo9hSv4X>qME&}?o0Q%JuJ7ByIW60lq3=5 zy0LO%=aZ^Y>)MTt-7oX$s4f0g{;ozMZ?5!Qh0R*~<FS|rp-If@8t~-gi}|T7eZZVO zJceuvp@oH)wuofcPy^ZXQM=8G>o<xVPBAeDn>rE;U$tbkFjR9r@9t?`Ex~_Sv(~NC zp09D1FYjh3(D!boEwGrCNee9emh<3s{+kqVtOQKmOuFPtJ5{5{@Y#h<Q$0OSHhOKR z9@{|2iRT83v%8@_K8<tDx-Z&v5IZ}i>8U7YkMnay_Y?2>(yD~;sJwy-elwv|X=&ME z0#96(Z;!^=nXzu5U<EwvG5g$`;#`|byV{YOa{$fbLPIluE~KVLNl9V1@P&uX@|u&L zeq8xaEhh4I;li+9Zcv5oKF&?5`Q*J2g^V|HE*s;cAyrn>Bi}~IEQcMIrpQz7r|CVp zugR;R?wnMW%fB`L0K)bGm(rRnI4RxxLo=bvhU6<!HXYyNDFM=bhxw-Y8Ex^fr7y|3 zECz-!I5lZ1M?7x-OLDS~s3Z`LL(N}lWD;ZX$RyJYm^HHHY-4ge>wLoD0SYIIuDod) zjsEY1xqEsHdn`IgU&O?puMLMv_FWt<CKh|U4Gn!Mlu9);?PAYSNsLO2cEJ4t-B22B zm0|G0JNGJym~EQ2u!Xth!JR@`5+;0;Zh2e^T;sun{nh0#rJO~lZ4XoIv{7`P_qM#e zm?NFu-WJx=OJ(a(D;r`|wP_)AgX|Xra;Q=6;uHXb0=eoHr_MrW#$Nb*D_u3)6HlV& zP+w`Ww_(Lg_ikf5IVxnYozp~WYMR(>>Q@aUq1(;pi!}bH%YWj#o&<5N_E5l5k6J?F ztExKSUPaZaGs`jI5%oK4p-JOj1PaT0iLTjl)bLaekat5%f=#>CE!5t;%+A#nM!<yL z&8|yv+HFh*Md5`+&gu-a;J;bgo2T*EtbiC^)5S{Kb;o5B#wa6o2}>gOR1W;Yq5Ou! zVYukf)Kqh|E9uPbGKCb>S1rTAz{&uL65PCz@-XqmJXIxy)^S~wzSB$$<uth$RX_{Z zI$RNMbpf;<qt&7#Ud?Xf{re#1p8ZxCh@me(l8}p_iI6Z78A^EpS*au-b6%MFsZp%m z8RP%vE3)TfhGf!Se~<iY)_2o_oKVNXk&}b*O4Cp!{j_Bo8H9n)w{LG0g<>qMSNi<f zu__kIaXtCSF*WET5E*WU-R%MXM=eT98tu(7`C6`M{0bN-rvWJZg*6<vf6F|t7U)cD zqxo93S_%q-o5MO>?<tN&7nXY!i}eSY=wT!;k3d=;zE1bZzWFva|C%|>Cxhl#;f-XQ z1U0o8=|l3?PYhd&)lPjEWz|j=;^Or5PiOD7Fg1Xx!CgxuiM(J6VNk986yK&j^sHtE zH{nG9wU?*olk^M)_KQElOUQpN>@%)q0clolHZjSq?w+xDVj4VSKEo~<wbCzKAk75( zeR0Zadi!&BcKB20hSO!Hi86_WMU=ZcP)SnSI@Q$?ySwEFSyDTcEkWn|%1mL^=t3L* zx56kzwI<6L8E6IM=XrD*W*37@DlWICn^eAPnoWN4H57Qr^YU9%*7C_b4TM$rcVfZ9 zl0a_>9s`-P1vw>VF)p9$aQNfnj4uw6)Vaf^W^(|E@3GrhITiaXN;C;jxmZnMD1Q!@ z(HpD}$M*yZS&h8>qr)A-gqkx9cWS^aP$6~goe@8@XCs12wGkAJvd1)GmDan5QBPG9 zqMP#ljP_uJ8nyp<d3N^-xL))=AK%KE(~B2TetyTtYkE2U6L+XHC*Oh^hvA-%_4n=H zK#kk}Th$?;>rNBlGt<8}*Be23j{UEHxv86Z0ki=m=w$z1svIMn-v#56{#w^9DG}tS zmo6unMWM1)HZzt=L{GK*UT52nYcMh0zfaEN^m_kcMu|<zPJv7CN5&INMw{&H9FtWy zGr!(qUbkDhnJ3M6n7kI`tMyi(peq9)4bXtHjbJcVlX{%ex}%5ObG}0?EgY(1j}yG` zrMN8x^Y?%r8eS@yOju#PTsbhHeiOI|2+`e{Bnxn4lNpqEEiV9ZKKC9QYf5HE>p>k) zzL|~oqo`Lz#yMJQhw6>~(<@)S@{Zf$o9BhjcO*38YBj;;etJ{`5*tfR1veWXkCFUx zAAMU##B^=+-aQN3jhBpEjy<7IbOo7XqCx8UH7pTzl!oZE(6c<<&;XIoc3PgZI^^x` zkn@`J_@rlzIWCrI_}g{^xm)Hi9yL5<b%1f=OHy1dUz%GqcT|s%iIWpG{LB?Llyaic z;Ge|mbl4SrynAe5#86!|WV^v>P70Qr&ZH$BQxF%^?SFPsVq-B|<FbCTpK>M*5}K3D z?cQEKE6b$#9}4ND+;-CA1p+y!82X`!36YiR4NmI4&kU3MOmzav(^`Mm8E8rD^Nm+n zhCMG@Y!4fwWt^W`-c_Im<$8y_D#T`?4KT1`OKV9USM_%D;M}v(W}k%AcNtdr^K)O4 znOt4RBs}LF1sP?fR?yQhJ(2Q`zb2~H>3oQdVuq)FfaXkDu-H*h$P*W%^sPD8ezj$7 zx$a3WTj$A+Kn={7mKQJCHkMX%MxW{y{o5ix*ZUc%Sf2qp%JU@Bbc|M6KpG}Sm#HeI z7~Uh#Bt5^Vz$U@W%;x#I!~9Dy5GBr2za4H-k{tdb%kOk^RsHz^J)8w#u6OU=g%7zL zJyX70rHegpDlXoW>2z}-KkY23H=W0RZ|Ny}85kRgsKNXE12hW%y6*H4!p|nfkNng! zu`7wWNj#ht4ir`{NBgFJ%4Yk{27L(xOn^zy>sVP*s*~s*B28S+n=>qyO22RU#?5}% zUQL1q=u_cdka&~vx}H_UKz}4$)80&=*3tPi$D?yY`PfQ%5D=Mfr&Aw9#+`Qd=qjn3 zG~ZVr7ciw&{&W9#f|L9eG7JU2zRrCc){!FGn>zMu`4#M_+Stq@G5S*nvl2c0-Sg)N z1N99)XZz}8kB@x%VhQG7WEnx^FpU=bXpQi3M8oWwPv8jnh|p8PWkBf9H6A6svc0jo zmL}1z`zxPqT>S3+e055{<Ya-QB;WB~IaG>&3-pxCbnK?!&9&j;&mc8|trK1rm&}vz zt9)T$TASXx@8>(WK~x}xOulnF_I$q~Ct|d0cd+A|n_ED|55W@LlA@yIC$OCY&G{&E zsaHwR>YqPp6F`F}jcyDL-DqcN_-pv5i(Z-b3rDvYk#U_Co04M1LNU?5j~TH`R8scJ zid|mb<l0y{fQqEx#FX0DEh^_o*(pY)Cvf}_A+_R6r3i~;pgg&LeOphg-TX_JVH+VI zFvgJJBZtn0bkLUokg=}`NW!m+jTfb3Qv>Bres>qA`3PR6Y~X!@dB)0<{+0hvs|VDy zc75{4Rc&b-dgDr6d3{Y%;968l0`7Q?(_w&g0#FT}P6+Zj48v-0vxf2Rt+aL}9&YRt z;A?)dWsK@Im=TI)Kc9OAPCS%gKX_ikV%)KN=%|**NwdN>fS$vjxZdJ*%7y^aeds&t zAcCh6R!*yLl(YfNlHT@q*y#2>&}ynb>(9xe<0emxE3>eQF`|*K8mZyYLiVlQxVaCe zfJ}J&+F!j2pMCDIb8~$}#nEAVv4og69xo-mGn!HDNSN4$0ESU_<A4i79HWx~d$A$T zfp{4pr9Yp#DK+{lBsL;G)WJOwu3qaZ@Ew1(0GB5(3){yR3~d03MG*Uf;xnL6L$u4U zBq9Ogi7#?KRs%WCdPOMAcXMn?93I7_q>j^HwtbL3aA9O3AuhlO>X3!Gu276td3gwK z)+AnJIStiE9y-hKsjmRkpkK$Xye;aFPY@NB1Vo~XJRQ+evsc+iUzyd*@`ratjiSb@ zvX)<K$K=><oPkblONQ{``9&r1Bpe+>7YpkqY99ebJ$~pP(fI6^Ur^9?wU~UFe<su7 z@9pObRFxl%tvv6Gdoyp5+gO^;8g8C_43eAF%v(35`<hm+Vr!0cA0N<y%5V2zOW*hA z+Z@+1uzYY<*{<?FjhUF_sYX{=h78qU-f5}bW|Us1Iki)Cl__)>u4ZQ!O1WS81J)=i zf{59DohLtXhttvjVQawxK<}+ll|OCDzeUg6k#Q;d<<-F|FSgL5%6GexxN1J$zyYxA z-kJ@BmH2<EfZ%vvw8+jj&Suj~6C3NY7w?jv_;+!EHvgM1gV(wYhwSluCI~a4Wngp9 zjhw&7FDs-x`X(uj#JGaee~#?u?q}pLz3+P6rja&?b-<+n(rHwSZW9<M0zI?)1p)K) za3tZQf3EEl<sC<iBe~>nM}mlkuWBAR%!wg8OkrTEi&E$=s?-jxLJV2|o&W#KT!WxD z?LXER&7eXu3UpL6nZck+4hJt!Fg<W2NnmEK3sT{_moz>($56-vxstUp-0T6tM{-;Z znIXRk3wTP&x=riC;SEQRDQ;}T_e0o8>1h8z5g%}u`Sy=puWves!MFHc@%)WJGb1UJ zE_v(702m17h5RO>{BJ1WC!}~!;DQCBy;x1Md9}BRJHcm$V1N$=8(hF-iOvn(%mFRz z00VbiN>*Hym%#S{5qypgQ(P_htAPc8YGN>lfOS(>ED;^n7oF5S!1S*ZX9&Jo!K7bW z*o>~P0U8QGLsbh{YA0aTZL)4AlfgXHg}?}@doH@d9+yO`XUc-JP;epMRayDxg_z8^ z_xn59{$-z5{77P!zR3~L?Od0cq&XtDm^~Lad<higq<1L)Vm(5EgkeXgJEt&~lIZH0 zAU?wh;nhKQ`$prwr8wy*ru)H34Y+J&w!cRPw7+p3xc!?2Jes<G%r=gL^xOTk%%b4y z&fkL@R-A)R=_i)@j7)|8qHyV5?wJ?d-g5pg8tr6(?WB&i;7^*44w{a{L2Ho_4#_6~ zl%w<#&%dWfdZGQwGKJ@_L9AWTlVIN+iGou0YS(iQ?U4LV+Q+0EpVM(4wlpWjTFuN@ z4?@djYb`49FQ$+GsJMXuAR$GByB8}5Uf<g8&aTyFtZAH&WABwek&L(BoQ6wrJD+Zh z4?k6|G1otch>PpN@iiWuDr96UNh?3TpPvt~z9DPM$qHTRr15Xb0}Ty`zi-D1?6g;y z>XSjwU4ac@d<VZicBM6Vrph?7g=qUUy+SJQev<wcL8sqffsvk`lKr~s{O&hJ#lDsn zgg`t!dC!c~85r3BKmZi=T&1YQdM}NNK5BSCO6kRP=M9bwQttF_^_85AA=ezrD}4bA ztF>qUsZnDkVn1HJMg+5)vANlLi|Ss!+8{IS5>P1)balTZFa3paLVga0RJrOl#3W|* zL&B02j*mBH8%iv#Q-5tm@i^A45B=CNsB5?YpvyQEQ}(oMb7DS{=-!=QC8hSike3H* z$3<DogVm1pro%sBAV2G|*^%?E0YT=bPijf^IVff3`G9?7?*b4y`6Sf#{6tQ9xEy1k zOy%k8y$9R}HY{Hc*Vn%3H8UnuG*T<mP^&sRbo(kS4z7(TEk5eQ-MESdr-1=oz6duw z1l3Bm@2Z|^ldOc5)rdbLhPavZk37VBTWZA3knU-zS+C)(-=<i73I}8r$aRF2E)Cba z3eJ+*_%aO0=em|CVy^_|OFwpY*ojv_z<E<hRK7;anKxVKMcD<QX9Zuruyfm`qhydF z2Ee<mLYR&^KJkD?c(|u;(_31;=R7WN#jvD8jf0h5+V6R*MRvBum}>5j#I8+EKL<5> z_TrpQsx|E_T&GbT4b3oA(Z7P#`D71ZJtM?o65LdTo17fM9Ml{Jl%{CJ{M@{y3solC z{u$#<Fy<}wYPx_<jqBk^^Yokfjf)Guwgia?BdYfw3ry7h67gF6)!38lrm}c77xQI@ zxZ-`kOq3g6&R#2ME*M&=1U>lCBd!-Xkf83h7TNEkUKR+Yw4+AqdgDgFKGwp=->VKI z|GKK}YYe7_nikf{NhOJvD)qy{wK5C``tnDLN4w-B%|=qYeRB#vsb0P|xGLUoQjV(H z;>U?v;kK;vK=Wfr0F_*6_U&MiRfq0*k(0#6aEiLZtD?9OCJo+S+4J}3R%U2Gk|HMg z5#+}opF3RoA$+OUoJnhcsiSL;!m*?{+;+;s>5y9M9p~lgZYPj~nh-<R?}xhU7lqK~ zDoP_qDk^0zmuVuY!#*d6eAO>}Rd0oDsv;?BNXPg%G$<i~T<LYs+_@J)bJ`N5EwF~< zaNVS#m-;qw>|Pv;R}UbZbU-a@^+nAAHRHzQ6xM{qYzO7e$@@xVeAan3NG*&w_)7Jp zJZeQa6S}5$ROBU*K?w)?{gQpWOA)Q3&5Msc;Z)mu_T!Q}rUF!6%qk?6l$N`J=-gl+ z%fRr2r)Tg`u+ld7lC7kP)U1Jk^x-RkHwkUM8AcDY4AqS3TPdN*uTCBrl7uSDRrn=p z1;9!)eosi%8k$zo(fQDuN{c%V_l18x;}TZzG~VPv{6)Rk(3n#@Gd3{sM|SJ`y4&>w zG(vk$%D5ge#g@kXm%)_K7e9ffZ(8)PqvbXPq~kqfYlh}XFvZ(&?nr>2V7T!7JBu!w zN%m!3p(5XI9i{R5&A|;iQT(Nu!)sSwBJhPgD-NTSzC|k^L~cRNy_Pp#9Vc)(W|eWr zn1n?Lp&+X$BwS4+`Ia+VZ|SosU3^wwQwNhQevD|l6%?fe_?l1DNQIey_+Z)tz<5u| z*3l7|&I1CUxW)L47WM3jEl?u#I|&wjO<GVBfFGpfZ$35v=q}aqmhla{d~Ntl<OqJ} z!GxwsnZ--H>AJp5?`r}d4kmtqxyhNXJ*P7^wusjTV;<Mp==B2cdWBK?CYG8S9VkPS zOPH!$6MBjV^gm)MECT{;9PSb~*En`)L(iAkldsdTRhB2;t(Z6RF9Tk&=4SGEanqV5 zSsC+Rk}kiLo^&+}tk&kfejPc-6wZ<87Ru$Yu1b~$T0$$grUpwhp62|LCKhgJwBFr) z7naJKDyvTA%961i_lR1kjrcjEkZA3VE4khk3&UN`4eA>eFd96oV_@)_o`$s1?bHm6 z@F=$QD=HwxtZC1CcP$0kinih5zdBf(n!8e3TJ)=Q7#|976Fh9WN3*>r38t||%7}-D zcssBaTM0b_%Gl?>C@7RL3bs$_dTYc^6TEvDqHO;m&>N3U|KDcL-bkCllrwO*F=b`^ z0vAd%X3CnSF#=<N9TXy&Ee_6nQ)f!&{nCy5^Fsn2q~L%q(v-APc5TV3V&}=(>)u0~ zXHUyNZ&?GN7b<YBvJl0i9=M?Y;Bd8_ujE%VVYf*<pWTG_WM6UGP3;#AcQnpH@B90E z!h<7Bi0HYV;o<bJcHPT?$qFm4kChp*%%po-7pJziZmUBf_!XdZck<fh$Be-0n7lDs zI&{B0U^BTlPx{ieDxC?cZJGb&{Z5hI=I~HgcQKGEbF#{CUB+_;0ny;e&z3L5V*ALK zY6nKj9-bYct1zi|xHa}XN}k@Jjw{jj1hO)<6>&4+=$#=w*?NnxyNl?eqDA*Y8Dm$? zZr-%5|0<GZnEl!eXXFX3m+Am$5?*ar;byA!-mu+SG+b1H&R{aBI?;AD)?p@ivn>lW zCN0^HVRJCYJ}`Pt6qk(i(WCJe7?xfRL3QWf$+|(4Nx6!42z<e`;8*~k^RyV>@tWhc z!{{221CC*J;^nu55v>)kHKiBc^%amSpXddV0g2|>qAVrcj{N0OTfHYYcA@rYseY%Z zrU@l{y3;EoTs&<|PNg2yy>gn#;Qts=i(NTgDLEo5``t{NGoWvY2X(7>xZFbFzbd)y zx*3PD+YP?`nRli7v)eVOdpD9(i<$OwGjJ^jOCNx-ip$=HlUXL|y3u<HetwsiR<%bk z))&sm`DdM%r>g9^;VZj(U>fWhE-RNeIKU?ktFO5PyZAO5I}&DhyrknA7-&GdnEtHH zK!%HHpy@`+7q_wHCcDDSrRmv5AWKP#cGZ)-kW?>T0^BvWIp2M(#wm*a(qVG~jF?Ow zY*f?HWo70{TB?}RO|a;j^4SNci8kYBdJYy~w#7B#$6OZ{?jt7QAJ5m~yUoC5TeJrz zJXa6bo<HOW?hAM+nQvk3tcRXiyWGDzwLR-AVSu?-EAiNGpdjOb5y@k9_l0#?sj#7) zy?#7bRJC~{^Xy`0s*J|V53zjis-A5q_T4k*nQQp-rzZlomtVqmdh)F$S5eN4j;Te& zr*QKe%fh`J2bD7Rq5SP;Ttq9w?*BLARFKamz-~pq9?#yHNb@}<AP<#C5$X9}%F+}s zOs8MyJ~x$XwPa-j*0LHCn{MEC3s}z|tk2yGr!IfD?jPVyL7p5xq~)fhe(zze=-CmK z8jedScA-GY<9F)qKrN}%UA+AO9IC>iY~NTjBl8K#10nZ=k2eZGh$38cKH5SRr7V}% z+(G?2EnUMu1Hdu!x@|x%u!xKO!2V&AM`ekoXItKIiJx!V4FYO5-3>7eW|f~9-s4i# z+k2_)0N}w#Qv*_{MyV0Tt&HIs{-?!GcCIpt-uhDO*%}I)YO9>a3Nm?mO$Ls%X1_L_ zDy=xpuekzJOS3jkAAuKskrA-Zmz&qInAq0QZfIs8fp)i=GO%C0#0*X%ddlqd&AF%K zro-*pEFPoE$&w=b9IK4>BHQ1;rb=a<TIE>MIsmQjm6zQ|styijodc({9SJBEdpFbp z^gWZi$Kh;6i~C><kgnQBBBlK3henG7J-0>@hhQ47AE6LZOuYt-e8|yRUCHmV)GX0k zNA?TwwVFfF<0{!cu*JrrKu`Z$dp|aA)hn`dZ%TMBPZ`Trl4IMx+iHuAiN9oMGPu|# z%agVKL7oeY)<6j9>_^LEf0!G@k&qXR-(*+xqPUt#Y(|Jp4W;75R03hm7=FGxQ5{Oc zes`Cs-?phQtFwWjb)#W1S-LwnOSl9R`B#;psicHlH+#L|ZP;Vb_lLNPeZbASv-a=} zu6dmuI+6lr&B1<1q|ke5y?SuE>(pXR44OWZpm)B}(>|${FUH+X3fiVt`w<$h{j_~! zBlCLK`_xYEo&^<m0~HS<1jgYKzFDu2B*l|99JfZ1RJjP~W{Mm*3lBcmnd|6Xq6Jfg zA(d4II#xhQNGvEZywMiNZqc7DK9Pdghr3^PfEq&A+x$U@{bzB@aI7cr04QL9WohZW zuixL-@JrXutS$L?AL+Lu4h7Rk6!LqQ6!b{KkGRV+-~$WAyEQsS-Qd>-7ke(+-!yN4 z;T`Y0wr?RP{O7fzNo*-be~GBYk<!|~I|9#Qj*Dhhu)kOODP>s`<Lb&Ec#!u~PNwu~ zqhR6>{KmxaC)UP2Df}O`Fu=wCfBg;-{8lV^;qsc@^%61$hZ8^4Z^vE<OAF--zV-ND DmZRo= literal 0 HcmV?d00001 -- GitLab