From f8838ccecacef25399c8af1716f90eec6abc18d1 Mon Sep 17 00:00:00 2001 From: Luiz Aoqui Date: Mon, 21 Feb 2022 20:48:16 -0500 Subject: [PATCH 01/12] rfc: commit inicial do RFC-001 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Trabalho inicial do RFC-001: Migração do bot da agenda para a Azure --- rfcs/RFC-001.md | 261 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100644 rfcs/RFC-001.md diff --git a/rfcs/RFC-001.md b/rfcs/RFC-001.md new file mode 100644 index 0000000..fd94ba9 --- /dev/null +++ b/rfcs/RFC-001.md @@ -0,0 +1,261 @@ +# [RFC-001] Migração do bot da agenda para a Azure + +| Data | Responsáveis | Colaborações | Aprovações | +|-----------------------|---------------------|----------------------------------------------------------|---------------------| +| 21 de fevereiro, 2022 | @Rehzende | @DiogoMajela @selton00 @afernandescneto @phconte @lgfa29 | | + +**Sumário:** Migração do bot de agenda do Discord do Heroku para a Azure. + +O Discord da Mentoria IAC possui um bot que auxilia no agendamento de reuniões. +Atualmente esse bot roda no Heoroku mas vamos trabalhar para migra-lo para a +Azure usando o App Service e o serviço de Postgres. + +## Histórico + +A Mentoria IAC é organizada de forma dinâmica através de reuniões marcadas por +qualquer pessoa que esteja a fim de trabalhar em algum item do projeto. Essas +reuniões são coodernadas no [Discord](discord) e o agendamento é feito através +de um bot. + +O bot monitora o canal `#agenda` esperando por uma mensagem no formato +`>criar_agenda`. Depois dessa mensagem a pessoa que iniciou a agendamento +informa a data, o horário e o tema da reunião. O bot então envia uma mensagem +de confirmação e qualquer pessoa que colocar um emoji na mensagem recebe um +aviso do bot na hora que a reunião começar. + +O código do bot é escrito em Python e está disponível no GitHub através do +repositório [`mentoriaiac/bot-discord-mentoria-agenda`](bot_repo). As agendas +são armazenadas em um calendário no Google Calendar e também em um banco de +dados. A interação com banco é feita através da biblioteca +[SQLAlchemy](sqlalchemy). + +O código é empacotado em uma imagem Docker que é armazenada no serviço de +registry da [Heroku](heroku). A imagem roda como um dyno na prórpia Heroku e o +banco de dados utilizado é o add-on de PostgresSQL. + +O deploy do bot é realizado através de uma Action no GitHub que usa a [Action +do Heroku](heroku_gh_action) para construir a imagem e subir a nova versão em +um passo só. Essa pipeline não possui nenhum tipo de validação extra, como +ambientes intermediários, verificação de vulnerabilidades, etc. + +## Proposta + +O bot será migrado para a Azure e utilizará os serviços App Service e Postgres. +A configuração dos serviços na Azuere será feita usando o Terraform. A pipeline +continuara utilizando o GitHub Actions mas irá extender os passos para separar +a parte de CI e CD que será integrado com o fluxo do git. + +O resultado da pipeline será uma imagem Docker que irá ser armazenada e +distribuída pelo Docker Hub. O deploy da nova imagem será feita com a Action +[`azure/webapps-deploy@v2`](azure_gh_action). + +Teremos dois ambientes na Azure, controlados por [slots do App Service](slots). +O slot de `staging` será atualizado toda vez que a branch `main` do repositório +for atualizada. O slot `prod` será atualizado quando uma tag nova for criada. + +### Ideias Abandonadas (Opcional) + +#### Manter no Heroku + +A proposta da Mentoria é de sempre explorar novas tecnologias, serviços e +práticas. O Heroku já é utilizado para outros projetos da Mentoria e surgiu uma +demanda para explorar mais a Azure. + +A pipeline no Heroku também é muito simples e não segue algumas práticas mais +estabelecidas, como validação da imagem antes do deploy. A migração para a +Azure irá permitir mais flexibilidade e expansão das etapas realizadas na +pipeline. + +## Configuração da Azure + +Para rodar o bot é necessário realizar uma série de configurações para preparar +e criar os recursos e serviços necessários na conta Azure que será utilizada. + +Essa configuração será feita com o Terraform seguindo a prática de +infrastrutura-como-código. O código Terraform será organizado em um estrutura +multi-repo, composta de um repositório raíz que irá consumir outros módulos. + +- `iac-mentoria-bots` será o repositório raíz que irá consumir os outros + módulos +- `iac-module-azure-app-service-plan` +- `iac-module-azure-app-service` + + +```hcl +resource "azure_resource_group" "bots_rg" { + # ... +} + +module "bots_app_service_plan_linux" { + source = "..." + + resource_group_id = azure_resource_group.bots_rg.id + kind = "Linux" + size = "small" +} + +module "bot_agenda_app_service" { + source = "..." + + resource_group_id = azure_resource_group.bots_rg.id + service_plan_id = module.bots_app_service_plan.id + # ... +} + +module "bot_giropops_app_service" { + source = "..." + + resource_group_id = azure_resource_group.bots_rg.id + service_plan_id = module.bots_app_service_plan.id + # ... +} +``` + +- terraform para criar resource group, app service plan e app service +- pipeline de tf + +## Pipeline do Bot + +A parte de CI irá incluir várias etapas. Os primeiros passos serão feitos sobre +o código-fonte, como linters, testes estáticos, testes unitários, etc. e irão +rodar a cada commit de um PR. Quando o PR for aprovado e + +Na parte de +CD + + +- CI: lint, testes, deploy on merge para staging +- CD: promoção para prod + +## Configuração do banco + +- tabelas criadas automaticamente +- estrutura + +## Configuração de Slots + +- servidores Discord diferentes +- estratégia de slot + +## Tarefas Futuras + +---------------- + + +## Outras Seções + +Desse ponto em diante, as seções e cabeçalhos não possuem um formato específico +e ficam a critério das pessoas que estão escrevendo a RFC. Seções são marcadas +com nível 2 de cabeçalho (`## Cabeçalho 2`). Tente organizar suas informações +em seções auto-sufcientes que respodam alguma pergunta importante, e ordene as +suas seções de forma que o conhecimento necessário seja acumulado (ao invés de +forçar a pessoa que esteja lendo a pular entre seções para adquirir contexto). + +Seções geralmente são geralmente divididas em sub-seções estilizadas com +`### Cabeçalho 3`. Essas sub-seções ajudam a organizar ainda mais as +informações para auxiliar a leitura e discussão. + +### Implementação (Exemplo) + +Muitas RFCs tem uma seção de "implementação" que detalha como implementação +será feita. Esse seção deve explicar uma visão de mudanças de API (internas ou +externas), mudanças em pacotes, etc. O objetivo é dar uma ideia para as pessoas +que estão revisando de quais sub-sistemas precisam ser alterados e área de +impacto dessas mudanças. + +Esse conhecimento pode gerar recomendações de formas alternativas de +implementação que talvez sejam mais idiomáticas ao projeto or que resulte em +menos mudanças. Or então pode resultar no entendimento de que a solução +proposta nessa RFC é muito complexa para o problema que está tentando ser +resolvido. + +Para as pessoas que estão criando a RFC, detalhar e escrever a implementação em +um nível mais alto pode server como uma forma de +["debug com pato de borracha"](wiki_pato) e pode identificar muitos problemas +comuns ou até problemas que nem sabemos que não sabemos antes mesmo de escrever +qualquer linha de código. + +### UX (Exemplo) + +Se essa RFC possue mudanças que irão impactar usuários(as), é importante ter +uma seção de "UI/UX". Mudanças que impactam usuários(as) incluem mudanças de +API expostas externamente, mudanças no formato de arquivos de configuração, +mudanças de saída na linha de comando, etc. + +Essa seção é efetivamente a seção de "implementação" para a experiência do(a) +usuário(a). O objetivo é explicar as mudanças necessárias, qualquer impacto em +relação a compatibilidade com versões anteriores, qualquer impacto no fluxo de +trabalho, etc. + +Como a pessoa que está revisando a RFC, essa seção deve ser analisada para +verificar se as mudanças propostas se encaixam na experiência estabelecida pelo +projeto. Por exemplo, se a mudança de UX está propondo um argumento `-foo_bar` +mas todos os outros argumentos usam hífens, como `-foo-bar`, então essa é uma +mudança que merece ser discutida em um comentário. Além do mais, se as quebras +de compatibilidade não são toleráveis or existe uma forma de fazer a mudança +preservando a compatibilidade, essas opções devem ser exploradas. + +## Estilos do Texto + +Todas as RFCs devem seguir um estilo e estrutura similar para facilitar a +leitura. + +### Markdown + +As RFCs são escritas em formato [Markdown](md) seguindo a +[sintaxe do GitHub](gh_md). Cada linha no arquivo deve ter menos de 80 +caracteres para facilitar a leitura em modo texto e comentários durante a +revisão da [PR](glossario_pr). + +### Estilos de Cabeçalho + +`## Cabeçalho 2` dever ser utilizado para títulos de seções. _Não_ use +`# Cabeçalho 1`. + +`### Cabeçalho 3` deve ser usado para sub-seções. + +Os próximos estilos de cabeçalho podem ser utilizados para seções mais +aprofundadas, mas é raro quem uma RFC vá além do `#### Cabeçalho 4`, ou que até +chegue no `#### Cabeçalho 4`. + +### Listas + +Ao criar listas, é comum que a primeira frase/sentença/paralavra esteja em +negrito para dar atenção à algum ponto ou categoria. Por exemplo, uma lista de +considerações para uma API: + +- **Formato** deve ser widget +- **Protocolo** deve ser widget-rpc +- **Compatibilidade com outras versões** deve ser considerada + +### Blocos de Código + +Blocos de código devem usar três crases seguidas e realce de sintaxe quando +necessário. Por exemplo: + + ```hcl + module "meu_modulo" { + # ... + } + ``` + +Irá gerar o bloco: + +```hcl +module "meu_modulo" { + # ... +} +``` + +[azure_gh_action]: https://github.com/marketplace/actions/azure-webapp +[bot_repo]: https://github.com/mentoriaiac/bot-discord-mentoria-agenda +[discord]: https://discordapp.com +[heroku]: https://heroku.com/ +[heroku_gh_action]: https://github.com/marketplace/actions/deploy-to-heroku +[slots]: https://docs.microsoft.com/en-us/azure/app-service/deploy-staging-slots +[sqlalchemy]: https://www.sqlalchemy.org/ + +[wiki_pato]: https://pt.wikipedia.org/wiki/Debug_com_Pato_de_Borracha +[gh_md]: https://docs.github.com/pt/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax +[glossario_pr]: https://github.com/mentoriaiac/glossario#pull-request +[md]: https://pt.wikipedia.org/wiki/Markdown From f76bae864ec8a3bd434ee0d3f65ff5ee87a279be Mon Sep 17 00:00:00 2001 From: Luiz Aoqui Date: Mon, 21 Feb 2022 21:06:32 -0500 Subject: [PATCH 02/12] fix: corrigindo formato de links --- rfcs/RFC-001.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/rfcs/RFC-001.md b/rfcs/RFC-001.md index fd94ba9..e2052a8 100644 --- a/rfcs/RFC-001.md +++ b/rfcs/RFC-001.md @@ -14,7 +14,7 @@ Azure usando o App Service e o serviço de Postgres. A Mentoria IAC é organizada de forma dinâmica através de reuniões marcadas por qualquer pessoa que esteja a fim de trabalhar em algum item do projeto. Essas -reuniões são coodernadas no [Discord](discord) e o agendamento é feito através +reuniões são coodernadas no [Discord][discord] e o agendamento é feito através de um bot. O bot monitora o canal `#agenda` esperando por uma mensagem no formato @@ -24,17 +24,17 @@ de confirmação e qualquer pessoa que colocar um emoji na mensagem recebe um aviso do bot na hora que a reunião começar. O código do bot é escrito em Python e está disponível no GitHub através do -repositório [`mentoriaiac/bot-discord-mentoria-agenda`](bot_repo). As agendas +repositório [`mentoriaiac/bot-discord-mentoria-agenda`][bot_repo]. As agendas são armazenadas em um calendário no Google Calendar e também em um banco de dados. A interação com banco é feita através da biblioteca -[SQLAlchemy](sqlalchemy). +[SQLAlchemy][sqlalchemy]. O código é empacotado em uma imagem Docker que é armazenada no serviço de -registry da [Heroku](heroku). A imagem roda como um dyno na prórpia Heroku e o +registry da [Heroku][heroku]. A imagem roda como um dyno na prórpia Heroku e o banco de dados utilizado é o add-on de PostgresSQL. O deploy do bot é realizado através de uma Action no GitHub que usa a [Action -do Heroku](heroku_gh_action) para construir a imagem e subir a nova versão em +do Heroku][heroku_gh_action] para construir a imagem e subir a nova versão em um passo só. Essa pipeline não possui nenhum tipo de validação extra, como ambientes intermediários, verificação de vulnerabilidades, etc. @@ -47,9 +47,9 @@ a parte de CI e CD que será integrado com o fluxo do git. O resultado da pipeline será uma imagem Docker que irá ser armazenada e distribuída pelo Docker Hub. O deploy da nova imagem será feita com a Action -[`azure/webapps-deploy@v2`](azure_gh_action). +[`azure/webapps-deploy@v2`][azure_gh_action]. -Teremos dois ambientes na Azure, controlados por [slots do App Service](slots). +Teremos dois ambientes na Azure, controlados por [slots do App Service][slots]. O slot de `staging` será atualizado toda vez que a branch `main` do repositório for atualizada. O slot `prod` será atualizado quando uma tag nova for criada. From 6dbecfef44e327e0c809ffe05dd1661e752c9dc7 Mon Sep 17 00:00:00 2001 From: Luiz Aoqui Date: Sat, 26 Feb 2022 21:16:38 -0500 Subject: [PATCH 03/12] =?UTF-8?q?rfc:=20finaliza=C3=A7=C3=A3o=20do=20RFC-0?= =?UTF-8?q?01?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Proposta da nova configuração dos módulos Terraform e descrição dos pipelines de CI/CD. --- rfcs/RFC-001.md | 362 +++++++++++++++++++++++++++++------------------- 1 file changed, 217 insertions(+), 145 deletions(-) diff --git a/rfcs/RFC-001.md b/rfcs/RFC-001.md index e2052a8..ae5bd27 100644 --- a/rfcs/RFC-001.md +++ b/rfcs/RFC-001.md @@ -1,20 +1,20 @@ # [RFC-001] Migração do bot da agenda para a Azure -| Data | Responsáveis | Colaborações | Aprovações | -|-----------------------|---------------------|----------------------------------------------------------|---------------------| -| 21 de fevereiro, 2022 | @Rehzende | @DiogoMajela @selton00 @afernandescneto @phconte @lgfa29 | | +| Data | Responsáveis | Colaborações | Aprovações | +| --------------------- | ------------ | -------------------------------------------------------- | ---------- | +| 21 de fevereiro, 2022 | @Rehzende | @DiogoMajela @selton00 @afernandescneto @phconte @lgfa29 | | **Sumário:** Migração do bot de agenda do Discord do Heroku para a Azure. -O Discord da Mentoria IAC possui um bot que auxilia no agendamento de reuniões. -Atualmente esse bot roda no Heoroku mas vamos trabalhar para migra-lo para a -Azure usando o App Service e o serviço de Postgres. +O Discord da Mentoria IaC possui um bot que auxilia no agendamento de reuniões. +Atualmente esse bot roda no Heroku mas vamos trabalhar para migrá-lo para a +Azure usando o App Service e o serviço de PostgreSQL. ## Histórico -A Mentoria IAC é organizada de forma dinâmica através de reuniões marcadas por +A Mentoria IaC é organizada de forma dinâmica através de reuniões marcadas por qualquer pessoa que esteja a fim de trabalhar em algum item do projeto. Essas -reuniões são coodernadas no [Discord][discord] e o agendamento é feito através +reuniões são coordenadas no [Discord][discord] e o agendamento é feito através de um bot. O bot monitora o canal `#agenda` esperando por uma mensagem no formato @@ -30,30 +30,46 @@ dados. A interação com banco é feita através da biblioteca [SQLAlchemy][sqlalchemy]. O código é empacotado em uma imagem Docker que é armazenada no serviço de -registry da [Heroku][heroku]. A imagem roda como um dyno na prórpia Heroku e o -banco de dados utilizado é o add-on de PostgresSQL. +registry da [Heroku][heroku]. A imagem roda como um dyno na própria Heroku e o +banco de dados utilizado é o add-on de PostgreSQL. O deploy do bot é realizado através de uma Action no GitHub que usa a [Action do Heroku][heroku_gh_action] para construir a imagem e subir a nova versão em um passo só. Essa pipeline não possui nenhum tipo de validação extra, como ambientes intermediários, verificação de vulnerabilidades, etc. +A Azure é a plataforma de serviços na nuvem da Microsoft. Um dos serviços +disponíveis é o [Azure App Service][app_service] que permite rodar aplicações +em diversas linguagens e ambientes de forma completamente gerenciada. + +Para rodar uma aplicação no App Service é preciso criar um +[App Service Plan][app_service_plan] que define os detalhes de ambiente em que +a aplicação irá ser executada, como sistema operacional, CPU, memória etc. Um +mesmo App Service Plan pode ser utilizado por vários App Services. Recursos na +Azure são agrupados em um ou mais [Resource Group][azure_rg]. + ## Proposta -O bot será migrado para a Azure e utilizará os serviços App Service e Postgres. -A configuração dos serviços na Azuere será feita usando o Terraform. A pipeline -continuara utilizando o GitHub Actions mas irá extender os passos para separar -a parte de CI e CD que será integrado com o fluxo do git. +O bot será migrado para a Azure e utilizará os serviços App Service e +PostgreSQL. A configuração dos serviços na Azure será feita usando o Terraform. +A pipeline continuará utilizando o GitHub Actions mas irá estender os passos +para separar a parte de CI e CD que será integrado com o fluxo de trabalho com +o git. O resultado da pipeline será uma imagem Docker que irá ser armazenada e -distribuída pelo Docker Hub. O deploy da nova imagem será feita com a Action -[`azure/webapps-deploy@v2`][azure_gh_action]. +distribuída pelo [Docker Hub][docker_hub]. O deploy da nova imagem será feita +com a Action [`azure/webapps-deploy@v2`][azure_gh_action]. Teremos dois ambientes na Azure, controlados por [slots do App Service][slots]. O slot de `staging` será atualizado toda vez que a branch `main` do repositório -for atualizada. O slot `prod` será atualizado quando uma tag nova for criada. +for atualizada. O slot `prod` será atualizado quando uma tag nova de release +for criada. -### Ideias Abandonadas (Opcional) +A configuração e infraestrutura criada para o bot de Agenda poderá ser +reutilizada para outros bots, reduzindo o atrito e o trabalho necessário para +criar novos bots. + +### Ideias Abandonadas #### Manter no Heroku @@ -66,196 +82,252 @@ estabelecidas, como validação da imagem antes do deploy. A migração para a Azure irá permitir mais flexibilidade e expansão das etapas realizadas na pipeline. -## Configuração da Azure +## Configuração da Azure com Terraform Para rodar o bot é necessário realizar uma série de configurações para preparar e criar os recursos e serviços necessários na conta Azure que será utilizada. Essa configuração será feita com o Terraform seguindo a prática de -infrastrutura-como-código. O código Terraform será organizado em um estrutura -multi-repo, composta de um repositório raíz que irá consumir outros módulos. +infraestrutura-como-código. O código Terraform será organizado em um estrutura +multi-repo, composta de um repositório raiz que irá consumir módulos +armazenados em outros repositórios. -- `iac-mentoria-bots` será o repositório raíz que irá consumir os outros - módulos -- `iac-module-azure-app-service-plan` -- `iac-module-azure-app-service` +### iac-mentoria-bots +`iac-mentoria-bots` será o repositório raiz que irá definir a infraestrutura +base e consumir os outros módulos. Nesse repositório será criado o resource +group ([`azurerm_resource_group`][azurerm_resource_group]) onde os outro +recursos dos bots serão definidos, os App Service Plans +([`azurerm_app_service_plan`][azurerm_app_service_plan]) necessários para +rodar os bots e um servidor PostgreSQL +([`azurerm_postgresql_server`][azurerm_postgresql_server]) que poderá ser +compartilhado entre os bots. -```hcl -resource "azure_resource_group" "bots_rg" { - # ... -} +### iac-modulo-bot-docker + +`iac-modulo-bot-docker` será um módulo Terraform quer irá definir a estrutura +padrão de um bot da Mentoria que rode um container Docker. O módulo será +composto de um App Service ([`azurerm_app_service`][azurerm_app_service]), dois +App Service Slots ([`azurerm_app_service_slot`][azurerm_app_service_slot]), um +para staging e outro para produção, e, se necessário, um banco no servidor +PostgreSQL ([`azurerm_postgresql_database`][azurerm_postgresql_database]). -module "bots_app_service_plan_linux" { - source = "..." +As variáveis de entrada do módulo irão definir os detalhes do App Service, como +nome, a imagem Docker do bot, `app_settings`, `tags`, se precisa de um banco de +dados etc. - resource_group_id = azure_resource_group.bots_rg.id - kind = "Linux" - size = "small" +Adotar o Docker como opção padrão de execução permite simplificar a interface e +a implementação do módulo, uma vez que não é necessário suportar configurações +variadas de `site_config`. + +### Exemplos + +`iac-mentoria-bots/main.tf` + +```hcl +resource "azure_resource_group" "bots" { + name = "mentoria-bots" + location = "Brazil South" } -module "bot_agenda_app_service" { - source = "..." +module "bot_agenda" { + source = "github.com/mentoriaiac/iac-modulo-bot-docker.git?ref=v0.1.0" - resource_group_id = azure_resource_group.bots_rg.id - service_plan_id = module.bots_app_service_plan.id - # ... + name = "bot-agenda" + image = "metoria-iac/bot-agenda:1.0.0" + resource_group_name = azure_resource_group.bots.name + database_server_name = azurerm_postgresql_server.bots.name + app_service_plan_id = azurerm_app_service_plan.bots.id + + app_settings = { + "KEY" = "value" + } } +``` -module "bot_giropops_app_service" { - source = "..." +`iac-mentoria-bots/app_service_plans.tf` - resource_group_id = azure_resource_group.bots_rg.id - service_plan_id = module.bots_app_service_plan.id +```hcl +resource "azurerm_app_service_plan" "bots" { + name = "bots" + location = azurerm_resource_group.bots.location + resource_group_name = azurerm_resource_group.bots.name # ... } ``` -- terraform para criar resource group, app service plan e app service -- pipeline de tf +`iac-modulo-bot-docker/variables.tf` -## Pipeline do Bot +```hcl +variable "name" {} +variable "image" {} +variable "app_settings" {} +variable "resource_group_name" {} +variable "database_server_name" {} +variable "app_service_plan_id" {} + +# ... +``` -A parte de CI irá incluir várias etapas. Os primeiros passos serão feitos sobre -o código-fonte, como linters, testes estáticos, testes unitários, etc. e irão -rodar a cada commit de um PR. Quando o PR for aprovado e +`iac-modulo-bot-docker/main.tf` -Na parte de -CD +```hcl +locals { + database_connection = var.database_server_name == "" ? [] : [1] +} +data "azurerm_resource_group" "rg" { + name = var.resource_group_name +} -- CI: lint, testes, deploy on merge para staging -- CD: promoção para prod +data "azurerm_postgresql_server" "db" { + count = var.database_server_name == "" ? 0 : 1 -## Configuração do banco + name = var.database_server_name + resource_group_name = data.azurerm_resource_group.rg.name +} -- tabelas criadas automaticamente -- estrutura +resource "azurerm_app_service" "bot" { + name = var.name + location = data.azurerm_resource_group.rg.location + resource_group_name = data.azurerm_resource_group.rg.name + app_service_plan_id = var.app_service_plan_id -## Configuração de Slots + site_config { + linux_fx_version = "DOCKER|${var.image}" + } -- servidores Discord diferentes -- estratégia de slot + app_settings = var.app_settings -## Tarefas Futuras - ----------------- + dynamic "connection_string" { + for_each = local.database_connection + content { + name = var.name + type = "PostgreSQL" + value = "Server=${data.azurerm_postgresql_server.db.fqdn};Integrated Security=SSPI" + } + } -## Outras Seções + # ... +} -Desse ponto em diante, as seções e cabeçalhos não possuem um formato específico -e ficam a critério das pessoas que estão escrevendo a RFC. Seções são marcadas -com nível 2 de cabeçalho (`## Cabeçalho 2`). Tente organizar suas informações -em seções auto-sufcientes que respodam alguma pergunta importante, e ordene as -suas seções de forma que o conhecimento necessário seja acumulado (ao invés de -forçar a pessoa que esteja lendo a pular entre seções para adquirir contexto). +resource "azurerm_app_service_slot" "prod" { + name = "${var.name}-prod" + app_service_name = azurerm_app_service.bot.name + app_service_plan_id = var.app_service_plan_id + location = data.azurerm_resource_group.rg.location + resource_group_name = data.azurerm_resource_group.rg.name -Seções geralmente são geralmente divididas em sub-seções estilizadas com -`### Cabeçalho 3`. Essas sub-seções ajudam a organizar ainda mais as -informações para auxiliar a leitura e discussão. + # ... +} -### Implementação (Exemplo) +resource "azurerm_app_service_slot" "staging" { + name = "${var.name}-staging" + app_service_name = azurerm_app_service.bot.name + app_service_plan_id = var.app_service_plan_id + location = data.azurerm_resource_group.rg.location + resource_group_name = data.azurerm_resource_group.rg.name -Muitas RFCs tem uma seção de "implementação" que detalha como implementação -será feita. Esse seção deve explicar uma visão de mudanças de API (internas ou -externas), mudanças em pacotes, etc. O objetivo é dar uma ideia para as pessoas -que estão revisando de quais sub-sistemas precisam ser alterados e área de -impacto dessas mudanças. + # ... +} -Esse conhecimento pode gerar recomendações de formas alternativas de -implementação que talvez sejam mais idiomáticas ao projeto or que resulte em -menos mudanças. Or então pode resultar no entendimento de que a solução -proposta nessa RFC é muito complexa para o problema que está tentando ser -resolvido. +resource "azurerm_postgresql_database" "database" { + count = var.database_server_name == "" ? 0 : 1 -Para as pessoas que estão criando a RFC, detalhar e escrever a implementação em -um nível mais alto pode server como uma forma de -["debug com pato de borracha"](wiki_pato) e pode identificar muitos problemas -comuns ou até problemas que nem sabemos que não sabemos antes mesmo de escrever -qualquer linha de código. + name = var.name + resource_group_name = data.azurerm_resource_group.rg.name + server_name = data.azurerm_postgresql_server.db.name + charset = "UTF8" + collation = "English_United States.1252" +} +``` -### UX (Exemplo) +`iac-modulo-bot-docker/outputs.tf` -Se essa RFC possue mudanças que irão impactar usuários(as), é importante ter -uma seção de "UI/UX". Mudanças que impactam usuários(as) incluem mudanças de -API expostas externamente, mudanças no formato de arquivos de configuração, -mudanças de saída na linha de comando, etc. +```hcl +output "prod_slot_id" { + value = azurerm_app_service_slot.prod.id +} -Essa seção é efetivamente a seção de "implementação" para a experiência do(a) -usuário(a). O objetivo é explicar as mudanças necessárias, qualquer impacto em -relação a compatibilidade com versões anteriores, qualquer impacto no fluxo de -trabalho, etc. +output "staging_slot_id" { + value = azurerm_app_service_slot.staging.id +} -Como a pessoa que está revisando a RFC, essa seção deve ser analisada para -verificar se as mudanças propostas se encaixam na experiência estabelecida pelo -projeto. Por exemplo, se a mudança de UX está propondo um argumento `-foo_bar` -mas todos os outros argumentos usam hífens, como `-foo-bar`, então essa é uma -mudança que merece ser discutida em um comentário. Além do mais, se as quebras -de compatibilidade não são toleráveis or existe uma forma de fazer a mudança -preservando a compatibilidade, essas opções devem ser exploradas. +# ... +``` -## Estilos do Texto +### Pipeline to Terraform -Todas as RFCs devem seguir um estilo e estrutura similar para facilitar a -leitura. +Os repositórios Terraform irão usar GitHub Actions para executar pipelines de +CI/CD. -### Markdown +No repositório raiz `iac-mentoria-bots`, a parte de CI acontecerá a cada commit +e PR. Serão executadas tarefas básicas de validação, como `terraform fmt` e +`terraform validate`. A parte de CD acontecerá na branch `main` toda a vez que +uma tag de release for criada, e `terraform plan` e `terraform apply` serão +executados. -As RFCs são escritas em formato [Markdown](md) seguindo a -[sintaxe do GitHub](gh_md). Cada linha no arquivo deve ter menos de 80 -caracteres para facilitar a leitura em modo texto e comentários durante a -revisão da [PR](glossario_pr). +O repositório do módulo `iac-modulo-bot-docker` irá executar as mesmas ações de +CI, e usará uma configuração de exemplo para validação de CD que será destruída +automaticamente. -### Estilos de Cabeçalho +### Ideias Abandonadas -`## Cabeçalho 2` dever ser utilizado para títulos de seções. _Não_ use -`# Cabeçalho 1`. +#### Módulos para resource group, App Service e App Service Plan -`### Cabeçalho 3` deve ser usado para sub-seções. +Uma outra forma de separar a configuração Terraform seria criar módulos para as +partes individuais, como, por exemplo, `iac-modulo-resource-group`, +`iac-modulo-app-service` e `iac-modulo-app-service-plan`. -Os próximos estilos de cabeçalho podem ser utilizados para seções mais -aprofundadas, mas é raro quem uma RFC vá além do `#### Cabeçalho 4`, ou que até -chegue no `#### Cabeçalho 4`. +Mas estes módulos seriam apenas uma camada fina sobre os resources que estariam +por baixo, não providenciando muito valor em termos de abstração e organização +do código. -### Listas +Definir um único módulo que engloba vários resources que seriam comuns a vários +deployments de bots possui mais valor por simplificar como bots são definidos, +escondendo os detalhes do banco, slots etc. -Ao criar listas, é comum que a primeira frase/sentença/paralavra esteja em -negrito para dar atenção à algum ponto ou categoria. Por exemplo, uma lista de -considerações para uma API: +## Pipeline do Bot -- **Formato** deve ser widget -- **Protocolo** deve ser widget-rpc -- **Compatibilidade com outras versões** deve ser considerada +A parte de CI irá incluir várias etapas. Os primeiros passos serão feitos sobre +o código-fonte, como linters, testes estáticos, testes unitários, etc. e irão +rodar a cada commit e PR. -### Blocos de Código +Quando o PR for aprovado e a branch for integrada à `main`, o pipeline de CD +para staging será iniciado. Esse pipeline irá construir a imagem Docker e +publicá-la no Docker Hub usando a versão curta (7 caracteres) do SHA do commit. +Depois de publicada, a imagem será usada para atualizar o slot de staging na +Azure. -Blocos de código devem usar três crases seguidas e realce de sintaxe quando -necessário. Por exemplo: +Quando uma tag de release for criada, o pipeline de CD para produção será +executado. A imagem Docker será criada e publicada no Docker Hub usando a tag +git como tag da imagem. O slot de produção na Azure será então atualizado para +rodar a nova imagem. - ```hcl - module "meu_modulo" { - # ... - } - ``` +Os pipelines de CD irão usar a Action `azure/webapps-deploy@v2` para atualizar +o App Service Slot de cada ambiente. -Irá gerar o bloco: +## Configuração de Slots -```hcl -module "meu_modulo" { - # ... -} -``` +Cada slot do bot irá apontar para um servidor Discord diferente a fim de +nos permitir validar mudanças no código sem afetar o servidor Discord oficial +da Mentoria. +[app_service]: https://azure.microsoft.com/en-us/services/app-service +[app_service_plan]: https://docs.microsoft.com/en-us/azure/app-service/overview-hosting-plans [azure_gh_action]: https://github.com/marketplace/actions/azure-webapp +[azure_rg]: https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/manage-resource-groups-portal +[azurerm_app_service]: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/app_service +[azurerm_app_service_plan]: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/app_service_plan +[azurerm_app_service_slot]: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/app_service_slot +[azurerm_postgresql_database]: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/postgresql_database +[azurerm_postgresql_server]: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/postgresql_server +[azurerm_resource_group]: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group [bot_repo]: https://github.com/mentoriaiac/bot-discord-mentoria-agenda [discord]: https://discordapp.com +[docker_hub]: https://hub.docker.com/ [heroku]: https://heroku.com/ [heroku_gh_action]: https://github.com/marketplace/actions/deploy-to-heroku [slots]: https://docs.microsoft.com/en-us/azure/app-service/deploy-staging-slots [sqlalchemy]: https://www.sqlalchemy.org/ - -[wiki_pato]: https://pt.wikipedia.org/wiki/Debug_com_Pato_de_Borracha -[gh_md]: https://docs.github.com/pt/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax -[glossario_pr]: https://github.com/mentoriaiac/glossario#pull-request -[md]: https://pt.wikipedia.org/wiki/Markdown From e8c7162865f4c868bc8105f1caa452fc52f15c58 Mon Sep 17 00:00:00 2001 From: Luiz Aoqui Date: Fri, 11 Mar 2022 18:49:59 -0500 Subject: [PATCH 04/12] docs: remove PostgreSQL do RFC-001 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit O código-fonte do bot de agenda foi autualizado de forma que não será mais necessário um banco de dados, então o RFC-001 não precisa mais descrever como o serviço de PostgreSQL será utilizado. --- rfcs/RFC-001.md | 80 ++++++++++++------------------------------------- 1 file changed, 19 insertions(+), 61 deletions(-) diff --git a/rfcs/RFC-001.md b/rfcs/RFC-001.md index ae5bd27..9a1293a 100644 --- a/rfcs/RFC-001.md +++ b/rfcs/RFC-001.md @@ -8,7 +8,7 @@ O Discord da Mentoria IaC possui um bot que auxilia no agendamento de reuniões. Atualmente esse bot roda no Heroku mas vamos trabalhar para migrá-lo para a -Azure usando o App Service e o serviço de PostgreSQL. +Azure usando o serviço App Service. ## Histórico @@ -25,13 +25,10 @@ aviso do bot na hora que a reunião começar. O código do bot é escrito em Python e está disponível no GitHub através do repositório [`mentoriaiac/bot-discord-mentoria-agenda`][bot_repo]. As agendas -são armazenadas em um calendário no Google Calendar e também em um banco de -dados. A interação com banco é feita através da biblioteca -[SQLAlchemy][sqlalchemy]. +são armazenadas em um calendário no Google Calendar. O código é empacotado em uma imagem Docker que é armazenada no serviço de -registry da [Heroku][heroku]. A imagem roda como um dyno na própria Heroku e o -banco de dados utilizado é o add-on de PostgreSQL. +registry da [Heroku][heroku]. Essa imagem roda como um dyno na própria Heroku. O deploy do bot é realizado através de uma Action no GitHub que usa a [Action do Heroku][heroku_gh_action] para construir a imagem e subir a nova versão em @@ -50,11 +47,10 @@ Azure são agrupados em um ou mais [Resource Group][azure_rg]. ## Proposta -O bot será migrado para a Azure e utilizará os serviços App Service e -PostgreSQL. A configuração dos serviços na Azure será feita usando o Terraform. -A pipeline continuará utilizando o GitHub Actions mas irá estender os passos -para separar a parte de CI e CD que será integrado com o fluxo de trabalho com -o git. +O bot será migrado para a Azure e utilizará o serviço App Service. A +configuração dos serviços na Azure será feita usando o Terraform. A pipeline +continuará utilizando o GitHub Actions mas irá estender os passos para separar +a parte de CI e CD que será integrado com o fluxo de trabalho com o git. O resultado da pipeline será uma imagem Docker que irá ser armazenada e distribuída pelo [Docker Hub][docker_hub]. O deploy da nova imagem será feita @@ -97,24 +93,21 @@ armazenados em outros repositórios. `iac-mentoria-bots` será o repositório raiz que irá definir a infraestrutura base e consumir os outros módulos. Nesse repositório será criado o resource group ([`azurerm_resource_group`][azurerm_resource_group]) onde os outro -recursos dos bots serão definidos, os App Service Plans +recursos dos bots serão definidos e os App Service Plans ([`azurerm_app_service_plan`][azurerm_app_service_plan]) necessários para -rodar os bots e um servidor PostgreSQL -([`azurerm_postgresql_server`][azurerm_postgresql_server]) que poderá ser -compartilhado entre os bots. +rodar os bots. ### iac-modulo-bot-docker `iac-modulo-bot-docker` será um módulo Terraform quer irá definir a estrutura padrão de um bot da Mentoria que rode um container Docker. O módulo será -composto de um App Service ([`azurerm_app_service`][azurerm_app_service]), dois -App Service Slots ([`azurerm_app_service_slot`][azurerm_app_service_slot]), um -para staging e outro para produção, e, se necessário, um banco no servidor -PostgreSQL ([`azurerm_postgresql_database`][azurerm_postgresql_database]). +composto de um App Service ([`azurerm_app_service`][azurerm_app_service]) e +dois App Service Slots +([`azurerm_app_service_slot`][azurerm_app_service_slot]), um para staging e +outro para produção. As variáveis de entrada do módulo irão definir os detalhes do App Service, como -nome, a imagem Docker do bot, `app_settings`, `tags`, se precisa de um banco de -dados etc. +nome, a imagem Docker do bot, `app_settings`, `tags` etc. Adotar o Docker como opção padrão de execução permite simplificar a interface e a implementação do módulo, uma vez que não é necessário suportar configurações @@ -133,11 +126,10 @@ resource "azure_resource_group" "bots" { module "bot_agenda" { source = "github.com/mentoriaiac/iac-modulo-bot-docker.git?ref=v0.1.0" - name = "bot-agenda" - image = "metoria-iac/bot-agenda:1.0.0" - resource_group_name = azure_resource_group.bots.name - database_server_name = azurerm_postgresql_server.bots.name - app_service_plan_id = azurerm_app_service_plan.bots.id + name = "bot-agenda" + image = "metoria-iac/bot-agenda:1.0.0" + resource_group_name = azure_resource_group.bots.name + app_service_plan_id = azurerm_app_service_plan.bots.id app_settings = { "KEY" = "value" @@ -163,7 +155,6 @@ variable "name" {} variable "image" {} variable "app_settings" {} variable "resource_group_name" {} -variable "database_server_name" {} variable "app_service_plan_id" {} # ... @@ -172,21 +163,10 @@ variable "app_service_plan_id" {} `iac-modulo-bot-docker/main.tf` ```hcl -locals { - database_connection = var.database_server_name == "" ? [] : [1] -} - data "azurerm_resource_group" "rg" { name = var.resource_group_name } -data "azurerm_postgresql_server" "db" { - count = var.database_server_name == "" ? 0 : 1 - - name = var.database_server_name - resource_group_name = data.azurerm_resource_group.rg.name -} - resource "azurerm_app_service" "bot" { name = var.name location = data.azurerm_resource_group.rg.location @@ -199,16 +179,6 @@ resource "azurerm_app_service" "bot" { app_settings = var.app_settings - dynamic "connection_string" { - for_each = local.database_connection - - content { - name = var.name - type = "PostgreSQL" - value = "Server=${data.azurerm_postgresql_server.db.fqdn};Integrated Security=SSPI" - } - } - # ... } @@ -231,16 +201,6 @@ resource "azurerm_app_service_slot" "staging" { # ... } - -resource "azurerm_postgresql_database" "database" { - count = var.database_server_name == "" ? 0 : 1 - - name = var.name - resource_group_name = data.azurerm_resource_group.rg.name - server_name = data.azurerm_postgresql_server.db.name - charset = "UTF8" - collation = "English_United States.1252" -} ``` `iac-modulo-bot-docker/outputs.tf` @@ -286,7 +246,7 @@ do código. Definir um único módulo que engloba vários resources que seriam comuns a vários deployments de bots possui mais valor por simplificar como bots são definidos, -escondendo os detalhes do banco, slots etc. +escondendo os detalhes do App Service, slots etc. ## Pipeline do Bot @@ -321,8 +281,6 @@ da Mentoria. [azurerm_app_service]: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/app_service [azurerm_app_service_plan]: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/app_service_plan [azurerm_app_service_slot]: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/app_service_slot -[azurerm_postgresql_database]: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/postgresql_database -[azurerm_postgresql_server]: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/postgresql_server [azurerm_resource_group]: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group [bot_repo]: https://github.com/mentoriaiac/bot-discord-mentoria-agenda [discord]: https://discordapp.com From 31407a3c15161af0280833f4377fc66eb74d4acf Mon Sep 17 00:00:00 2001 From: Luiz Aoqui Date: Fri, 11 Mar 2022 19:16:54 -0500 Subject: [PATCH 05/12] =?UTF-8?q?doc:=20renomeia=20reposit=C3=B3rio=20do?= =?UTF-8?q?=20m=C3=B3dulo=20de=20app=20service?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Inicialmente o repositório do módulo de App Service descrito no RFC-001 era chamado `iac-modulo-bot-docker`, mas não é preciso deixar específico para bots, já que é possível usar o mesmo módulo para outros tipos de aplicativos. O módulo agora irá ser chamado `iac-modulo-app-service-docker`. --- rfcs/RFC-001.md | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/rfcs/RFC-001.md b/rfcs/RFC-001.md index 9a1293a..4bb6653 100644 --- a/rfcs/RFC-001.md +++ b/rfcs/RFC-001.md @@ -97,14 +97,13 @@ recursos dos bots serão definidos e os App Service Plans ([`azurerm_app_service_plan`][azurerm_app_service_plan]) necessários para rodar os bots. -### iac-modulo-bot-docker +### iac-modulo-app-service-docker -`iac-modulo-bot-docker` será um módulo Terraform quer irá definir a estrutura -padrão de um bot da Mentoria que rode um container Docker. O módulo será -composto de um App Service ([`azurerm_app_service`][azurerm_app_service]) e -dois App Service Slots -([`azurerm_app_service_slot`][azurerm_app_service_slot]), um para staging e -outro para produção. +`iac-modulo-app-service-docker` será um módulo Terraform quer irá definir a +estrutura padrão de um app que rode um container Docker. O módulo será composto +de um App Service ([`azurerm_app_service`][azurerm_app_service]) e dois App +Service Slots ([`azurerm_app_service_slot`][azurerm_app_service_slot]), um para +staging e outro para produção. As variáveis de entrada do módulo irão definir os detalhes do App Service, como nome, a imagem Docker do bot, `app_settings`, `tags` etc. @@ -124,7 +123,7 @@ resource "azure_resource_group" "bots" { } module "bot_agenda" { - source = "github.com/mentoriaiac/iac-modulo-bot-docker.git?ref=v0.1.0" + source = "github.com/mentoriaiac/iac-modulo-app-service-docker.git?ref=v0.1.0" name = "bot-agenda" image = "metoria-iac/bot-agenda:1.0.0" @@ -148,7 +147,7 @@ resource "azurerm_app_service_plan" "bots" { } ``` -`iac-modulo-bot-docker/variables.tf` +`iac-modulo-app-service-docker/variables.tf` ```hcl variable "name" {} @@ -160,7 +159,7 @@ variable "app_service_plan_id" {} # ... ``` -`iac-modulo-bot-docker/main.tf` +`iac-modulo-app-service-docker/main.tf` ```hcl data "azurerm_resource_group" "rg" { @@ -203,7 +202,7 @@ resource "azurerm_app_service_slot" "staging" { } ``` -`iac-modulo-bot-docker/outputs.tf` +`iac-modulo-app-service-docker/outputs.tf` ```hcl output "prod_slot_id" { @@ -228,9 +227,9 @@ e PR. Serão executadas tarefas básicas de validação, como `terraform fmt` e uma tag de release for criada, e `terraform plan` e `terraform apply` serão executados. -O repositório do módulo `iac-modulo-bot-docker` irá executar as mesmas ações de -CI, e usará uma configuração de exemplo para validação de CD que será destruída -automaticamente. +O repositório do módulo `iac-modulo-app-service-docker` irá executar as mesmas +ações de CI, e usará uma configuração de exemplo para validação de CD que será +destruída automaticamente. ### Ideias Abandonadas From c9b9751b74670da1a7dda5e8f9a38cfa238d99d5 Mon Sep 17 00:00:00 2001 From: Luiz Aoqui Date: Sun, 13 Mar 2022 11:08:03 -0400 Subject: [PATCH 06/12] docs: muda a estrutura para permitir imagens MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Usando sub-pastas para as RFCs permite que cada documento possua suas próprias imagens. A primeira imagem criada é para a arquitetura da Azure na RFC-001. --- rfcs/{ => RFC-001}/RFC-001.md | 4 ++++ rfcs/RFC-001/img/infra-azure.png | Bin 0 -> 29883 bytes 2 files changed, 4 insertions(+) rename rfcs/{ => RFC-001}/RFC-001.md (99%) create mode 100644 rfcs/RFC-001/img/infra-azure.png diff --git a/rfcs/RFC-001.md b/rfcs/RFC-001/RFC-001.md similarity index 99% rename from rfcs/RFC-001.md rename to rfcs/RFC-001/RFC-001.md index 4bb6653..72bdb9c 100644 --- a/rfcs/RFC-001.md +++ b/rfcs/RFC-001/RFC-001.md @@ -65,6 +65,10 @@ A configuração e infraestrutura criada para o bot de Agenda poderá ser reutilizada para outros bots, reduzindo o atrito e o trabalho necessário para criar novos bots. +A arquitetura final da infraestrutura ficará assim: + +![infra Azure](img/infra-azure.png) + ### Ideias Abandonadas #### Manter no Heroku diff --git a/rfcs/RFC-001/img/infra-azure.png b/rfcs/RFC-001/img/infra-azure.png new file mode 100644 index 0000000000000000000000000000000000000000..7775224dcb7e7e2182eca5df634541e82aef309b GIT binary patch literal 29883 zcmb5W1z1#V*EUQk-3S7blF~V(ba!`m4mB_|2#884ASg(SN)AZZfTW0&bVx}_4hPHs{QO+J0$h9o`g}sn0y4bFKm5Ghe1f8;H|=el96bMYD8$VR zbiiiJ$H#ti&ELVt*9Gi((-of}Hy<}J0&)-sKO3i;Z$){X+%yHP6jY&l0w8}`eg$QD zpc7;pzd$dCn>O|i{w{V7$ZrJ{fTjGIGtk84pIQ27$nnbh+CW_#1?*IvG=#WhoNk(f z44v)mgjE6*wR!opHGP}|gk_ldWeq*}LFz$}AYb4;NJHDx#6?%biC0x2P{l^u-$yjS zM$XAi*+<_?z{b|lUPMLC&QVBJD9FvtFTly&gWu6qMo=FF{8ZgTUS8QAc%$W~AQE6N zz|61cpyUq$UcmlPpl?M!5hoQ_BVQ*seI0&RM_U;uA3+@%5Ln6BUdu#Lm0wBE$;HD) zQ&3SwL_x^KP)9j{PfbnV*3neYM;>Azpk=2Z<7c9)>S`*iBx2(Y(p7?rxY`Sfim2$@ zt9t~R3i;T$s@rMGo65L3sG8U+dg<5!KhX&EQ1AxkAR7oW2?)?P1j*}(3itz)adY*v z^-$BZQ#RC+4b)U{vG=rrYTM{5DyjIX1gL|Zp!z-n8c=<0eMecJ09|7_K22UvBUu{- zZ#S@=vbMU7hKj5~po+4hs-K>$tcJ4!SjGhkRdx^56SdcZipqdBH4IIS0z5oaR=mNHH82nV=o&o-vE;!Cy20shM|I=tEWehzK)xVy-grk z&Op}21MFktr=ue)qNEQpR1Wkv<#+RywNVLFanTDBl~=Hrb+SJWcdMIr3~Vn^&#krM6ky}xrJ?O6 z4}o~7d)s*0*f|F23h3Jin?Qnsw82h-MhYfCcTg`r4_;kSS5X;FPjz3wEXdlpI*SB) zA-7e_M#n?I#a7r)$jHUhPaUl4sww0xYGCXJaTj(0wn#Qe9&D)WVIXWH%p0U?r(tg> zWGWP-FW{vJ^rNV*W~8ej;()XT5JeMPUY`JnhP}G5n}N29HdKjMS&J7U&ugUXp$T#J z5e!tfSCmusQ;?GraSCwuP=@OH3L+KCUCU5_-wgr{(pS;<67&;L)<){Qe*n@n@M=LF z`IUpby& zuXz>(JZF4jToE8xi$zsn`;^K|u+nXmjq$@%s)W0^R0%@kubwdTQ6*5>T~W(uY&}z~ z%*7&qc1X~-Gk0}Dd(0WYQ}dOuTizWJnS{&h;@;$!N^1_lQB z2BNxpdLz*~1&RrSmwIqF<1iuieUtvk&B#rL5q~eq>xb0J<>w2&^f}IzUETgTuXw=> zwcnP)KFi9#%M2QSzRN^3srmY)uH3>ot1iVW$1IOk7x;A4hJ0M4{<1lu%9Z3SobGq# zhzSS9&D%_c5)t;FqS0EVvC@Hiy)H`OzR=dvRb>0;4e2*S>g({V46AuNTWOv0zd*r$ z{`V<8it*bkJ!BEAQ>V2^Eo3vUg~lZP&vSwJGZtn)H+N-$9;M2nwW~AES=Iep<51HuC@>1s8QeNNY;8GAy-9U6jzgyc%izl z#SG&(qwYOXqvdG$G^SZ)g&fv8PI{{yv_mk7)*GZ8f$`-rO7!kx^1L&x4(RNxdRZk| zE72tt^3Ag)rj{;MObl2(&&nEia`=cu?MT%I`2LlZOP+?I408ShL{H2D-YxA!2nc*i z1lC(GOK*0XZ?&d6r?pR*DK07-mY9qOL)=g1alQm~{(vzDBuiNC^Cd6x&FSuEC4GA&((cO<;-P}KVe>OOm zB*)xQ)%0@NfHFK{jiNR7gVL2}>TkN`Iklf$Fl zK00TT;yfeoJy(WX@LM)kY45i(Nim$E_9F6k=+Df02UTfV&uDw;)Go7@rPLH(eF8Is zIRjaHW1@G1_5-lAMsdMhvRw2ZlTt&fmLW{LFW*Z|^z90a;P2-YCi4vZ3SAAadvPUw zXvso}c6Ym#Inb(?=;Ph+o>e|F5;#_#bTC#)gmzDCR031DQLe}3Qw-!`WuwG^r?mJm zmC>HwM_I&`nPQ^kgP(q(V?)j==I?AW*1-PiD_bIb~WG7Py8KHsqeIQK>78^yIpSk{mOpQW%AYdY=G1#Dy?T~Pb^xrw=~l^_EKoAeY83+QX{3i zNR+#1+;l3JMZExR3c>K{I<;hoVGvvFk6DPqB+Vx)id}w~%n%l=lFdm;s~0jxNcfmJ?Fg2RnZ~E~P+h; zA*guXokUgX%8#8WX`SyiJ&qmsIbt1Ai`#rb|B$#K4)X_*C+Z-#ZOm{|0h1H1)!`nb zSL*q18()(rR;e-cN}N2j@Qzn|i0QdxKEvO%gpHrhTVm}GX&6XPz1rlFhT)D{M0=g> z`)qVpR443Z5;&#`*gGE$gK^1_L2$7$W{otTAlv#L=yAOjmPO~X>`*onC9Q6>I6XN9 z+R4G{{_bb3N$Drno!A|68^LD2*j7--E4DzR-YTXX=Ck>J$nvI^uzj?c`GYgw-VcNf z*!AS^RTesLcOE$gg?LLwU#=v3m(G+V^Laiwq+z~ajXGx|QGHt{ZiIn8WIioEc8`}E zA!fyI8~+-)!Sse`-mOxP+Ck$#oqiyN*g6Yk>BS;5t#u24M3Ad_jIs1kF};1l3oTF- zY1Ty)Bibh_v26l?pN|veB34{)Om>9|$|4y5lx>v`XChdONN+8TkH>k?Q;B zNzZ?ML7a%v#8CYoKL8B8Ko(gKVYRYe9QCnw`nP#*2zKYiB^J*7zJ=L6%K!8$w^4Dd zxH12F3qYiw(MvCy5s!S8N&f47P_W-FCDtsE{p*jAn!t_)NL69(SK)uhiNLUBm0qMi zC#yh5Zo|z}n-XIwEF2>S>`*F@nEcNQkWGosMd6jC)|mgtz*PC?_dqX1 zO^5{X@keTZ*N>EHeymRJkRLnJ4(b9*|JAr|XBn5X{x)ET|8}e@OTTlXqVkScdtF1Um4L>%QG_@#s7z zDhS=5YAy(8zJAP8@qB!Kmi%NSh@jHJ=U*;GNt`_OLv|HP z81TEhRg>KUx)XFvQVw|ot!gw4`8#>THr`UPV=z2>!{Ncv z`de}TUe=dqsga0Ki~U_@xzjLMLyq0@nj0{}J46jih5;axsT7fuG2mGmHrUT)XXV^d zZ0Y4O5UwQDA$(3^I_Jxv95ak3(5{zG`Ju}G^6GCd-zkIr1H)dU)!aj+v+roVYfitn zmCsJZk{1g-IL>bO#wusCp9S@vfYP&gq!`|SQ$k#Q&m#N7OHy*Gn0J}Z#CijiANK@M z_afK`(|$W1K&<)(QIm($X_#7h&SZPR4u`#0PVW#$;U`XKn5VPcfp6@wtPal|d!<}2 zPp*0g?U;Ge(dsMxzp6tYM3y+nNo62P`unH03ugeQl@ zEyps2vx#QutUfq9Sxrs?JY!rsvz&V?17$nKcK%#p@_V(priR4Ol{Mqu8MQE7{^(uY zWT_MhQ~q|k@|Spd@eePJ_DTC&JFXZS>r$I}3wTbQY7sHoiWf#|-IS|8nB-1rdCuJ>v|vG8 z;gG_l=)2}tBC@4fTvJ!nvIOuHyh`jBPg`ZI85c9km~)=nL7RGa2U45U?Xk9-nB}Uu zU*HX2koFS2Q$~z)r-fwOW6eu4-Io{l;EN@j6!@@=GQcob{#RNwIQZZ0~WI%!_!=4 zq;Fhn>p|@l(UbGlcZ)~SPqrkQVSI2n~bdsI7Q^- z+uzH2jJcAgqUd}JgI!)^`Yg-7Iw@7{R2C{%y(Cj_(!Ub27^g==l!V8ZP)js{o5a)3 zK(9Hh)X5OeLD~LrqSB+0)(RR=JP}vfk%_+;S8wmt&Q3`N?@#HVW777-a2F@*Z|OL;3uYz*KHv4_#lInc1opK{t0ZSZ zK)L?y`?<8wAJF6T?wKU>KTD=Zl|=L7TwNur+o`@p!);k<)BSf~Z8*jfIkY@EL7XYA zePZn8r?9`nFr^`tPfuxabG5nR-wnZLkp{k5Hlcv)bXNcVy(XpA^fajj*kCNRh4Q8y z9LO8SZ5iXG=0uEHW}v~>r7h}LKT!J=ye5}S@{ zs}Dp4qK~#t+Ioo!w!`OrKQvV5WdqG|@VTEoT~$m^UFi%25e^w# z#<`Lc`+3A2G!|-_7}!g8Yi6beJfO|OSNcg}4>TS5+s+{FTkIiGL$)S>#BgA!3ya}c zap<+FDz{Ohu^hL(mZ`WujqyeERFQb3JwY>AvXUN~=}|qNYjm|{mg1}xZ#T_Tg6H{KuT$?ST5?iVo?`iID+Y|Dfk zbf>A%;{a2ne|9-pEZ`aG5KQ1q;=i zHKi>Dgz>6roCf6$tcdnWV-Wrv)4XujcG|<4h%S$%=vG1=y5tk^oVwXNNnU2-?1@Z3 zI-Y8uHj0WP!L*LLAV>^Ep_OOJm5zuBe)t+Tm(^m9PdBPT(-a6Z?9%Asc~C-(DiPA6 z>3S8J1z2c3AFNmIe$|-0ubo=o1236Xy2q29w5cC+WM-bp65iTI?}KTyHJU!L&2JcH zcKaCyudw{SH*6j=DJf;~(Ko!tBb`U>7N`n>~Qo5&_F~04md*^)ByLw&834YzLP;JSXJ^xi-{WrnS?>Z7)+oR7P z(kLLxx%{iBGi$0XYt)2_sbXmP8dR_K3j#5mIE*`PyAbs$iUp=#=v~1RgpvOQZ^$hW z!j3afUu`VR6KN*(rerP!I%n&$9>Kj)T^3yTK-f$(NO}@oo|mM#L29nPX}U6i^_G_R zkf2^CICw1AwG_6KD`|5Ua-(@C6L+>F&wZD52{pDN)qYr89b%**+^7 z-7gjsTIyn-?$3d1(A6ki4U~PkKYlVixEj zRQ?B{uSGdVTw6R#=vZ(cgb=rT^$wtqwhb(5+P=Eac(VOkZFHt8tysnPpyy5wT!BzX z-V*w0nf%&Zw3Riu6Sq`jdTOep1v%Ifn$ud6+>7%@#F+({hDR!p5j6}e$c&W^IQVGp z{_b|I@ji%SZ^xP+7C+R3E@*)A`qNjsC`jQZZ$nAs*BT!{&NM^yV~Hx;8!v1d7LdC< z9ktM00Dp8$@7lg%(F)A}wfZdDpng~~6yE_FbYcXLnGbiW%+C6=ut?GXjU8$9|8gr*#B{|3*IeS(l zqLV{96_M{HQ+?EKSm@PGq0X^(v@onBubsC6JeUP*x@ zFUIG-m%x5d(jlju0)SBBE_vSqU#C6J)Cr=Tt7*J3Cf3pUmR&_Iux*rdmr3*4H%#k4BNLq4^Ry6$6fp7J zRSB|$4H9#25Bq-+UtQa)fHYD|rTB^Gj24Vwn+}fX3&H(Dp0O>5lJSkw^7W&Z?z4e2 zp`+ila2tx6`5=k8uQ6lxABTr&>v^L}&ZUDF2F#fnSOMK!@t*PLY?K&hI!b5>_2vMrDyY+At>Fg5HK;?IJ)CH)acSjNUI@SkN_R zJmMKn4Bv7P(E!sHYTU)RmlP%)1>#AM({3;0{WEjsh4Q%U~= z%d({~h6>_i6yke=7H~cASe=~5P@nM}|BVvu+eeos9@8_4Sk{swF~J)E6U1oZ$$o0Q zu_hMJ*%QjQtR9->TK@7^DY@-MyZd~O1|5Vm9Ncp*P*dK~(3-x*pPPHCP`dZgAVHxE zA!>G_Z+7flGRBRo@W=glT0R<84+4#cpffP43tuB%gm#AsB9(;HS> zrd;ASl{%< zRRu=9+nj%0K9@Z;?OST5R31C$W0A>2ep;{5SS0xiGUSoC!pLZilp1+KcGQwWNi3}R zt^*RJK-y_Mvg0EN^WN0l8 z%a*kFW$%9jm0{V#KZ;;wB&~6$#EzIw+*uBK;_+n17CVpNy~I^uiEXU=kOGa!bEh+M zJBp)u4FH(Ue{fQQg-xSNYH}L90bm!KWpD^HTgVSPs}BxHSkl#Nd12dLS4<0YPq7cm z@8Hu4B@c?1k>x}PH+&tTBQR}E5rb;)fj-CV+~XNVZiCLLD??&4(YDO;6OMOCT*%J^ zbmY|YM@q7&=#(7vOygKPIly2g+CMtj11b#9PfGl6Gyvs6S}aqmHv(_#4K}w zH}>%72gThXt@j@?5dE?-RZ+vo>mEVH{Y?kA$W0d*IRTrbbE+u9hg$qj5(_0x0T(G< z@|hB7NaK+Q5W+~~5$bg8138cNHPacAv|%`4lyHAsR~r+!;^inw)Y{X#8DAyYDk8M> zx(Ig^an$snc^JPJe9)=USbRwn7yOJPr1(A^9N73p>a#;a8Ii8+j-`KPGYNFGM8H%Biv*k*$j}T+QqP`wfXBdTiM0@!HT^-9gKBg%hQkzl z<16Ok&}#9^J_C=JM-Ftgn|Fe(R_o;F@?|11;J zUzZVq+U{l^X0=m)&sTr%KBc!k8@Jb@!#`d&gT%)}h4(^5r=!*>jbw2F%RzFQI&=4r z<&Z^Mjt3gAV|)^K1AVf0HzthPASVy019_@GtN+2?i>5S>){e}Hznp2>wFNdovC8B; zYNqo7Fn1xJtI}b*yI;+V>ECIP^BH-US8#ODNVoZ`jJ=7WO3jk4XemC#^nOHw=@&vJ|xA+xKSA40r-2txm<1;`^j z3%aEfav?q<7c0Lt`@TiYp=xIlv!A1krGJS=E?}Q+r>=Fp#zk?{5gyb!Pcp(k0uJX|Ud*RR*6Yu1)Q}A4zj- z$HeZt$KW9iGZd@SFV$^5x%g&vQNN}ZJjE<*Uy~4D2SaLtDySJiFa#*a1k!PscdG#S zXv_FI#P%xi1@CdXIqUND8|NiXzI7@YU{E}H9S~nM8&3o{YC}ol_Q< z+Iw4W1#jGHISopJ)auc6^GIo)L9SL?Zm&pglup)6g)3pn2iK^YSS`qix9^K$1B*R8 zZEX7BOgNiGogVCn1fUe>X_?Yw43#p*W)rs#Pj7#+qIF0Iy)_6mDBGB+)TB8Wb;`rM z1>0qA!ux!lOWoXyo5*cnT1M>4eGG|zU@>eH8Z5{lwv-NnGU2GW`7D8fw!V-VNFQ=SAina{Ms^-3j} z-@W9iG@l`?!p%QKay9mCKCN5NLugZ{G~EP20c&Hsl>fIdsA*L7Ec>9AaP33VYFYKS zJ8wwiDU8W{AUZ!S(Iyv@8x>S`1Q!gN|aiSKRmRs?m5o{`*logz9pD>ZfRNP*a`(- zM;=Qd7929c3)AuSV|0MFjw_pysPM{Nj^JO99)LqzG2yOx4Nw4-NFvaI6rg{eSL&YL&g=S^hs{|E z)`bli7h7h-<@+s$@78B)){MEPctTt=X+6fcLrq%+?|V5k)B9i39^kDJr&-i#bh~H= zgGgjFfblngNrk{z)J{7(Mf=8#)TowgI*xE);zxQO244=A7*A3u><%}zOKu00>7g%w z5PY}Ka1Aih2F2Qxoe96VJ?x0@?xc-&f$2{G`ti)K;xGMiD^ov>1$6=`z15iIgWTsz zXrBOQ7G=%#vQ{t#>=L+HoAysMGLog4c0bbo4`syHv&D?Pj&!h%9-TVeIFe*NB45fh z;BEP{j;TKwA~9~FCsN)|Ta4Hk^nk5F6V%(!2WSUoI_cNK%8O?2Nc(JFMD*QAUb<6S z=>13_O5Bn`j6)S>F|O?>P)7uW_h}f)Z8%7L*FN!utb&BZ>oI`9yc~(Po(KaJC%|Ra zynl8J;AGuzY@A0w+9m@k22&A1kP`|{+9_LEqt z-+7IvOL1dEd2$m}r>mLZ@~~ecImNz%l17qe%PAZ6KN9tFLXTf>3>PTzGZ;9-02-w4 zGm^3Wh@|#D8(&jjz>K3Jn{YG?*jTu>OUz|U=pQvpHZmDTp%MKoW zc86n>A@<(V%Pt|FmcwK!KHKOF$>7?6{%CB{Q8_}%x5Beta**RGr;_l?gPb{`;N6zG zH51kMBX5K-k@8ChC(!($m`)!;7r6W)?Hr0&__VRpw0jIMb8&UucfY+8_%`Qa%W2~a z2Tb3{0`jtPHN#B;%)PVr-G8R6u<*5jQ!&2qq%@ah^JEFBODv7QcDlLG(`l!iP{E*1 z#&RS%S{~0}By}DBBQZa-PRpaAva<*~AQCkBsTC^X)tEN1cu}KuKQEqvPBtf_;oV zx~o&Av7w<{%aA<lG%K>q=6(try$iv6XbI@`t|>a!1EA;1X(ZvrY@59e0ieYccrpY%O@@)$ILi zg>MAqh)F&|#N>t^4l79pM^r?|(uGZ)dxgx0o&?f8$_d^P-MyrF7*d}cX!t%msQAm; zUJT7A%msMpc~C!(afLKKm7sDc;uGfK>4y3fIR^lD$aNT(aFD@02NEHFa3_c&X`Z8V z1hO?bOxil>_ZXl0-CW3l#vmy=m?xV3+j4Kpoo0#23QdVUc`Y(4zhQ;BdD;+!%+dM$ z)f{OB5geF)<=VE4*#77HV>(JSD;Lz+^Lsyfg0Czad zhXGq)c@J@UzPpR<+I_n5^@%ugJ>mt%O{~cEXnyf8RUjknPZ;R$N@@7{1~P7veR7L1 zxB1sb$wuqd1?<;yiq@z{-S<@wOXbOUF~9W%4=Cbu*x5>YvEF-4t|+sB?~s;QEBX*Z z@h#q?l$_t4=QG1fblWjP3ndy@c2}F)zwu?Ms9j-}EfR#o;=9cS!qd+8g`5WN#XQtG zZEWyIY9~5qt=bsomGf|~lQjFo(Mau#XNhOaks)M2bPY-uax!U0xT{6f19irqv$i^2 zlH19PrQNuonJ2ISMxTqd2r0mj3GKRL1f4joeP%*IeNSVYiVvUzl16LohF2yj{os8ZE9g_=j#$iPBGP!6;2u40 zv=D84q%(Z$W8dieoUo0tn0Xl#fre{F1pxs+gaj@3oGzh`q7#U;1v}%@T4ULS#G#8v z-n}R;eozCLv_KnOC;L`c+UEkJDR!7gAW^^6ca*#Ek;K-3+vSt|j)9iD!;tjxx35UGxY!eaPaN z2|T3XWQNCT)X%|ccoo7AJ()s#%{1=lwHV3Q%Zw$*Py#@Myl!i&EUC{@~@4S zQh@$h;&QH#L;|TsAf*Sy8!o-&-_vEOsoF^pLM+W~7r%}Unj6tE2{@-B>QU41Ai0mP zeVFuzEOzz9&9C|*6T|>`o3)fD022z=F2!4-hCnU4DQ z^~JW~wY#3lZgJiOvOmMe@ij^^f6`#NJ_RFrWLB?rJ)LEgdhvPLD!-)yOrbNOs(@0h z8GB*YLMS-+IKOPLlU9C4plTY3B?YN%>Pvo%XN_A~6BEcSmed1`_)AE#^mP(pI_h*a zfh$Ft3@HX=7nKH10J}Xd;y!T=3LoBpLLeb(h|Kqp!42O9hNVo zjc>Ad#v90VDYC=ad&DuYzJqoO<9Z44Eo$sk_G^e$r%rJz)}RtOtG*#1xokn}mi$X` zG4t(|t(8iy`pwiGK4OXBkvOve-C*$r-Uxf-wXg%Z;EJ2PXbu0Z(rAn~3_zPVlo;aq z8uZVe8*g_P(q9Xa8c>`VgPi=Ln*)w9HnxZykdYM;!19;Rl%B#y=H`;&!(7HwA0B46 z%I>QQ`I+>R2W3KLNA!c>VjqO)s%2f4u%2LR2AyK{Whj>7A8GPp zRyoytQ)Pa%HEM~!GhXiC{5O%P?VA>fMf(_AZz|x&^YzsmjP8!tmsPz~I@&N`H2jIO}KX_SA%`rcX%QU2|ieU*?0m)`#wL&h9MKja6#?MC!<(x!D=mdGwR6@V0DQvos${;~D>6NNR#3ubc z?`sZ0%6B@-Yjb$q0N`4bf%K&pSK8_WocV8F1k*+cPyA>(2NtGW=J8c z7P4#D<=Ce}h!q@xrT7SR(nB3!B!0Z}c7dHB!|~av2aHM|zauOvUx0fMLPU^f#tk#~s+2&4@+Bas7=?L8KL-^Hnb?2 zzFGUr=3{O<1p{9CmV`9^*1a93$D)ir^VtpZ09{y=AqWaL-}`BHs4ce~L!mxQydVbL zb@*|(fia|k;42_NqiqoDhj9SW+zc7lcDo<*oRgh2xjY@`c!a{lz(4`OBXp_b8|uL;fPov zD&$ks`TBBT-u&_L1)OGd&u4%qB@_+l{8a`rPX4qSaZG;~MqlVc_-N>h)p#fEhk6%B5udoew=5ljaUS10z2%#=q3!(jL@T2#`^*PUkoL%d z`@1)dP_A8;w`8d`KCt(}CCChzB-TShV4&A`nt(v!F2hADP-JjnX#DNYpJcB9u{YQ+ zKr7?DWll}$OGMXGyd=$Rvx@Ur{GB|4nfllPBC{_Gc|y(0?NS?@LtT_H0h=?Hb35ab zTPw}nn8;!J+W*&KRIT`d3XpGlIuAU*yIkBN*O89*Eihw)F+9Agp)6L>WN8haPcWV9 zydC+h3VKjDQv0*)@$s%M8K!L~|NPElT=ysQRH74GjTYy{iSb8VVwhsL>IwYVex_e% z7!F0xnSqTH;PAV(9chsb9KEO3);M`xTa`FfaE$_hGu{S}!4;erA)U|*rfhlG7vn$n9A!M8gg7)1xNDp1#r7&vya!BR zMH4}?DfL9;_t${q_YbzM6UMP>lJFQ)Tr`VbW(E)F3Z#{j*G-`b3CyDQykwA&Q95kz z3%-~cG3&xoVhF?ZD>X>pnfz7eT}!T6IMxBN4(QepZWM{(0lI`dADsqVz`&cQk`*(g zO*J~F(UmoC&u|?WWPi?+Lg8Sok>D4yK4>70l@|o_fQ>^wbQu z@Y@wNo}>r?oPS9$z?{ddUVVJi6FmQR7w2L9?Y-cw)fUsP3t8*?Td0g8&SJiOw60B- zA$bbgp08IL2-MeC;N!@%&y}obWju0wD>>v;X!fm>cDI^i?4I5dG@ z>4W4S(%}~0iZKR$(0psMTY#YauUSe$Og~*E5B=W^;$?{j&J5+~=cM3|!(GgPqHn>X zn8rUtti#6vRBMgI+!D>(! zpF}IT-*k4P8teDY>!O2}s&HSL$XZBJ?Z#GF@a>=6eU5-Vsc2Gxz#d=mEPFZUSI1Z)_`IDVy|9g#eS1BSac(-3lt@a|MbMBeYM+ zUo8^D)Q$R9Fm6jFTwF@W@J8pdE5DK}Z-mTWrfn8deV@@UnePpRt1n`E(L_6?@2aLG zaB~AF!aAIWsZtT>xe8#4w@w}lDQe_yMT5!sxV%05-Zvj=l$;HQICwvg+1s0S07@V> z8y}eyR^ks^a59W8rmjhW4N46y?Y+;3AaNF7S}Crl8wgCd8nYVn*ywFoLN(^EQa?4o z^nDjL9-#RPdl<_Tc7PfUgCnRzCesKhO=94NKvv!=Cn-}W8aSoz;l<*PN&1IPfwJC)xi)3KNblR(xN0qZmSiyTXDT!{PsLjPaO1g0lP7vfT5^%adcSk(JZl zGCH;&F<#3HyEjmZxpX2DnB`r&M)6)zLI4s;1rIZd0W~t5dF+g|M*Xtf1u!#?5)ud1 z$r`;z%1ue{H`+<7bTOfCwCmb9h5%uV;Z{^#$R48BXI58pQ&TBcp5XaUQnM28zml3V z0m&cxY&xhXixM)b@?|tE>hoXb4ulX9cyL?Pszb0&-!)WRJeb%7tma@T5)lmrJp^nW zY)}>$aB|H9sRiAZ2|%_vxTRz^;}k89(f4X|!l~G#kJ?oAj+hOqrQv)@6Hs?0>efv? zyK`#&&U;-h&$ELg0R)GzL>p z7-ycbn*56>911%BKj%4xJ{<&=W37a|=UZsDkcb}7t2NyoYKl4$Tb}AODhhDqHXs^d zQL&tS_b1WGkCegz-m2Tc`ZSStB!GsgMi`iZfD^ET$I&$}A4$(L(38QbszTP34o1!9 zx%$qJg_~LKTF#ndGoID=Pw1S<((MZo{?E)NRe>F`1+ZU@Z=aJLpvEvkgayDtdbga=#y|r0o}K4~7;Ok4JDdAovBy6U$BxU7g>ff=AhbF7n?#Lrws&7x z6qO};WV+%y6Er*)Hm`v{pPN~W0!O@biA<8AuJyz`s`~WZud$OK6)?3J7-{e8ky2K% zS|}z~1yn!Dv71u@(0leHj2}P)L2ah0Dt`hu>!ylfuK&z>a^L@x^^AZ2|D5$4;y1^X zdAh<4xV~Z9A=*iknAMsg+E)Vd_<;0j66O~%1JKG@(XxL0#@5NB4#y-A{Kx>v9pR0+ zyfLBDzJe)?P&pH-W2eCo31$ng2h|0IkfhDq_sCjgm8lg2NU_pPMp{Wb&O9$Tf@6G) z9oO)~Lr4SNM-G3kE82J2V#9gHW1w9jwp|^kuJZm%aasE55`$;(x!mRUmuq5JJ^ig; z07TyPBd*em=2cmgPeTR}R^lEkUp*XXnfry=n+q}u3 z-V(QDn@?^R{RH)n@f5SRW`(TZw(yIqGM7h&!BIj;F3RTssAYi@6x0J{lOhJB!9>k9 z-z;oqDvcYn(*xAV;ExaDj3}ifJt~7jzO<)JP*EG29PU5db{kdMH>|}Mo4CW(hgQhY z1|f>C>Q!7TM%)5u2W#MP)qH6%GEIQ~N}1FABd@d6-3rJGnaGd-olS)mEp9?|V?C64 zV1+JKmiQBqw7Mw?0}hLuN!b9uK$9BGUXI>EDO`ye#8r{1hOErR;(v%t+zEaO69LBr z?QJq|gzt|SZm<#7Y|qt2P5XXN^hu?%Qn!h7OFiIhzY5mGp6qk0X?{1y z9G;3=E=4;YWxWa6(wIE_E<3sZM>ZAO6;NDenCVxdH*@G*;{S8xS0QC2$B$c;04n^P z0N0J?*_Lsy%TB=`p`EP5)YKFdH+X(?3m|UR(F^&-OwsaQl1pBo`1@!q`F7y8S4+Da z_l3UM%l+p1&yp)$cQ3?7i$7Uxj-rS@NGNK?@}enyJ*Y|wuf|+~@XXxBM{-KmcHG8F zF4)(1fJPAPyv4Q3;`@k)txpoEN)CT1ICziaf32%0gX?HJWHAxZU6-!W!vpR5n22rx zrC>w~;qGIUKtLo!FmrUR!%6?w`w4vfq5;})3R%}~Imto7=5~`!HsQf1C`Ut*g6ZBs z!a-F01~$yD&zegBcSS@q134q0602}vQ@Kq8S?kqLT()!PzY9?RSC&y8NH8=5_uX<2 z5}YfdIoymXkRSsC ziw;_d#2lT!x(w)_Tje^zWqXZn^Zt@Ol-5XY7`ao9#JDf7H$({^W8hm ztl~z$pUHKnQG^da0QcvP0(qv9M{b?=1yu|oN{RQ1{O1ClAp5|YLB@6LdJ+w!F87kq$LMOk?w;SbahVJG!lGYc68_bD{=jCkP1C)3 zulqVBOzl~p=YOYbr>_JCH@(O+AQNuceBzfZu!(n>rD6FCS1z`c?)?7#4CHQLSJ$AposqwfBg8e@|C1owXyf`n(mJEJeF1IF2vlzVc(x~|Hn1qHvBDv&P!fB3mn!3zw38@@4xQrzWB#=W{z{tdwZSN^Z9zb7dWTxEx6oAmClT5 z;fvHo9Im1Ndqt|{<3rR&F%aJM7_MkA4)pfIt&Lo<7syY8zMkVM0Mwc;Z{^?}_!=Pf z<{y!~AwB{Jq_1j<5Jk~jRVad@OTgOfq?I?IegsZ%Od^bC8t2B{b;~YGbf@BQzEg&On+tTUxrN4~BlehlFCeex_rpShe1Jz0NT>#6 z?D~_z>*bje++DM?&TD|mUGT(E<>Sg3mSb{yKEyGr1wcJ_@kc=t3Fy?1t1vqDDnDO_*RMtSJoY$@D(PB0Rc~f)8N(%g zqa~8XBxH9E)=(NNk0J^ssJ#jX{wnfcnJNIx54xlN)9JGdRk-!GXgXVg4Yx4`V8Nal z3fE!gkZlxiuSoK(SFFlE5^q>FZ_##z>=5|JqG*f7WQc=h*($5ux_uN; z_>Idu=Iz8H>3o^Y-H&{JB^3-t_jB91eCb`BzP-LUrhb2Wp(=0#K9yoC>4Sn7w+Uu@ z;RrDmAg~mj5V>VB$5^5Z}OUURlh=@^;U6^5DHRhZpSTBMBe>bL!a;GjRR^;c)y@5|gHuf`eS zWoBh)hCLb^#EhRw!%BQdeasYWv2;dsw%K#tS>a*`!N)cv!B|@x^dsFL2x1^^<&)h? zJ8ln9=K}_$EZ*uh06OdJ&+LSCMSbp1Ev%}S8-Up{gVZ^|02=OysI+)SmuQdAxcF{ZOWy6)uiFet+{8^POic~H8G#mreAnpS4m%|qf=x7$s$skq7ngf z#ue?KT4XyGt=kzPdz)jCs8Z&#%^+aFWxPbDG>P5^)bIT1%#blleuzK{G~%q*Pi?fF zHRw9? zLV@)81?Ky${H%Vdy)LwK9b6disg9)x>i!e`-O+49^?@{4^^1Hk5X&I#NVq<)&(n=& z>M-gJY4-ka?ffD0qbn(5#Z?GCScg=%IZULVeW(rBz3!OiHWlO< zNqWy1#T(SeSE3cv{g-ch)K88ty{{j~v_$;? zTIP~%hfTW2hzDlVam>TchIX8;c@IgqO||U>sJEp)I6^SpK<3U5v01amt)!~+5YZ(6 z%%&;qVm)9N$#9Dzp@Jy3iw1=#G}8zHJq;n6yL`uTt+ND0(o*p|ld_n%^)Ykh0K*^< z$iXjuG2@%0R25L{{^1~)>nGBSCUg59qQh6z$z*Dgspvq6GNmFjDP&%$IB25iO1%NUgo25 zTO>YA1~nMQb(}F03>8*uxB%ANK?Cljy&wJrv{wsvi*6_#@*(CnGYnpZeq=WS2`3?h z90Cw46||2Ck4dOx-VqsoZq+I3`6VPCu-2@$9@o$lz|T3s2{OL$GJ)fW8z~%cn4la< zkP#|0MZ+J_1m)vsJ%hXVS!(R24mKAR$AIPX9xR1vcA3x`sVJU;-+s%nwXro<8pwZv zRFR+g7UhB2T8?A1(=Z`VL3Oj{GnA@2p_}3w-MU-RreHu z56d8qKpPn3)2b1JT@^ImO^k(y&WzNQEA3a8CXSbdYoVe_ujYq?nQ(kfx`(@@FxNg9 zLNMRm!WHH@TDdjM@!>qi7|i8N4fWF9uU3H=66#9x&2|QNO>M1aiiP2{gCN2S*K83Q z;X#zaK`b}h;yf-qLqo^;W;j;pUP<6}G+yZP*<@x{mn zEW_Psvq4#ej6Rlu?cJkGWV?hgdwRx=(~Cxk?Z?C|S3GoM>}m;C33ywrIDDNknC-X2 zEaeazhD3#}qi3UQ4l8|b0~v2XURZ9-2-up*Yq&B47?sv_j*vbMa)oizMhNL0ijZzi zAihCOQ$>Pc^4gclumwAzunp#gsX1CZ+Ojgi=DFxJUY*0(W}`t;w`eQZ1nq9MEPPO) zdwlmC)@%x%NFT<#+hle(I82N?2zdutMa39KrvuTX%%Fj=X}gy2>ZnJn>X^+TYTD!o zY0SNb@i69kFjWeaq?1n_A)SIH+xMKu=F);$)FcLX-tymT7LC9)yAT6iG?t6<%^qvk6VQv+0eRORzsale!illKV zlnx=wSR-Y5X`rW8EHy){wKehKbb3OI<*z|78D##s zWlcRiCUmfsn`C;Hy;Zi2o)u_VmJjvGUi{kip-{b}-1t2`?C#HYPr~rg4vUgXww@Rj z!RHBQCqO8$;O15s*kg0v$TzTB>E0W4zz~E9b!)CV7mN)G+p||s3UBA)^NscwKFeWa zX6v#N2i*vhMwI(c=JjP;u4Vk%;)L)kxg^xCK9v0I5Ll$e zD@^^cPfNV^bs;<-Dp@(1bWXK;Ba!6B)h{-sfLjBldfxRAH^W1#np|qKc(?T_*5*ia zUo6mSk@R7lP%b(az0-=`(I&3e7a*e5n`K{Hnwn^-x_H@jganGmmykb^Y{+~afO>#A zAJ#sQz`r-pxHsv~DTr8E6U=Pq8nfDWk#|K5c9Z&K)ntN0@O>=<4kUU3pvyM_?}vOa zoN5%BVO3<95L4P;_Q1AYW`tbGY0F1CLB=Ya(tjLbNawuZIrN=@it>P)+=o@fH0Z%jrZHt#Q zA))-ll7Z)`2XX@GA>PMx>#K&!UfEK9Pe;G5G_R#-(oz$vVI5` zmK08UzZ-+*6$S+(>}kDHJw`xjfTQpI@n(6>>J7;8$I^oCN9UMu*m0;JNsmVv!}fs_ zLylx)T&Lsj_WA~z2hFq2bL&a5+kLeyy2l8czZU)ZJkT}~{=gcbj`1S0aAN$E+4dpS zl$0M?g^`}u>lP#mLId&-HRG~Yf`?wCKfyY26w77Jk9Z!54?VjF#E(QqaZow}%N0qF zWrMLb(^7$1>e1xYj8gF6Ww|H(1ZF`L3-_24JWLRS_ICt4S~RxxYz*{W@Av~5O9lCw z!m=WixCkw0ZAZ#crP7>&WWgo~hMLg0P&r%)!s#r89@}DKJF>wsM!MND_t5@fHy4Nj zzA8ICc#G7>Z-)@PTIO&6f{dm~IK0Q=akS)pE;u{k-lm>VNw_&G4+L(v2^w!Xq_H}V z8%!iAh(v(t&PLey>g+>^QBgMCd}?!p-DvtIPFaGv38kGyt?-sVw>mJ`@;ESqO*q&Y z?g>Veodz%%3h~@daA{f5Rz3R;_z*8tMdO_|RUIN;+U^9~Uc~mHSG+WLVx%fbTXEn{ zNFbU4z@C&Ad1cxclhxajgFZ1ui0d(GT-Q$AVw{w3yoe)47> zhCDI^=9qA7oiF&sqf%DJd|og9$n=rKE4;466yJ+-pIxey>8=#9)m>E57TJJ z210!K`QRf?KdVYeC?!?g+IN7e*Z>%M?)u$$yX|5{K@hMD_flhNVQ#w7HndS!kxE<{ z+|eQ+aO*(xpq8smxc)J2c|;DKP{N$;?Zjn9n5%pAk(O{9NZ+71DM>u`W|ju9dV|DW zZ6HHr1NJnCnh~TV70Y#=GpIcra)EpXoCN?-hQ{%zrYLlH6gBj2CF$I$V-b9cUhpWq zX;?Bf9|%XV%;#NYv2Qcmxg+!?0?nq$Nj-KEK^01BZ432pA4nln(Ume{a-+`v@I{+MBQo~MgEeFv$Heggf-cipEiG-pn)v=X(uor)sra~q?plnp&i3Hp zr8jg~2U1@xaTIx{%Z$JO21eGQ4OsAL^)3NpVwx@E%D&)nwebhr^b;~G*BQ|71N8Rs zyJ5+U8%+I2`$eZ{ydO&gVoL4$%kC{YCy+4+2CrsIh^e5>qVtNVX*Rdf7tq-m3;>x# zpFj+LWAs%DJwa>6C2US`ul!FN08rnO7FyKRNg zO5?*Eh&0;37;shU2ID*!6Q^^BSpxzPh=OfGunZ^RNw#%fMp-x#AHg!W!-V&qKSD62 zj5t}V5g6Cnpal5;&TeGcxykotac9BzGXB$`=iKk3tevT{OhoA<=Pi9gsmx5b59TGfVNNXWUEGx^g zGTt^T&#Q@>GWHL=Gg?-aR*nyz-cPxF_9mnx%0rO!Ceu)^!8$MQmIJn!g9(Rz=WoSL zb7X~v8D1}NZz+*lW3A;0O6ay+=1nkd_mAUDIMOPU+nsX9&gFWcXg9sEydz%EFB<0B z%Qa{mR%ODC0M*+q#krmX!WLw^R?riz6gEs!Yt?B|>o)lNEJT*_ujb`AF)q=AzNaEn z!Pe!9$cJp0(}o%to%TFGghJ*!&bI3BJoev|&ZM2X^(F?Zw9zr%%LONSwpu?n9@#)} z-+0Gio1bdM;h6rksC^*z_NFy2kcolZ{FTCUWk$-hb&6`Z-%-4K-h>O~apo4GP+>7p zqoew+81}!+R~AXZKI2^xAXu_sUlvDrBqGZJlE{A+!_ky|^7-~>K(Rv@#gMK&WNV@H zms0?ZNoo+Fw*0TY!Lqz5;6f7s)L3tdV?%Rk{{prE=mF&WIKwFR&lH5qFLf8dPOLwr zyHD{Ar*B!bN&Rt{@KVYd$__IMNB!~`Ccf}@G`gr2MPZHvLv9bj3@!a)&(HTeDpKG^fCXhOyy=O z%?%v@zzTf$%i$CH`UeJkRKp1X{r1b*{%q~r`1r|0O5MjXE=r2h{@0|0rMS2MH}1x- zv(~?|p8r5OzyO2F_P<&e+kzDpcF!l26ME}$VorB41F*v7d9;V{)!bi3TAZ8}C(n)d zzv`29%m1xWjR_J#%Kfge`FLVn=EBQV;4}iQKYmd!K!^U1H7kH8p#qv5_CMxyDU28g zF+_IsUHtjHh79vWI(F?}g;f_?Im@32zoYlGgoA5hT+C_v2shj1VV>-C#Wq$S6ACT1-86??r# z(q(+$XQ`^#C+qPEk5k@NX|)o4IUBct^ud}%neZPrk`@gkkf#lS->MDr?6vA*$JpzU zu`N740TabQ5s&cfdDlLEN@5nMF{G4;9pI532nk>TCYx2aSG38d?AU~WJ14R(4pzlK zE5L_4ikEc5NQY>)+ogd9NO}tOm~W0sF+Tb?O-CM4b7CGDN=u4S|He*5r-g!W&!T>u z6JM0j`TCr61gUDRm(TS%t&1Spm?_}KTK-;pD1sF9vD8Qn)4%rO4aRZ6#1}iT-g566 z(y8I#f|39T>p;4H{IA}H|8gBIc5n!QQ2~c6;(pcO`Q?VG3;y}%;`%@`0Qby7e-76js?)YEGUrHMZ)X+^pFsajIV{(aw>Mz2V=!S-4*8x)N4CpG^S*tMd1SjT* z+*cma-w*csNoppXLHcGzyPFkx*;e`V>EHJ&K%ESHqxKARXakQ#q$SEAX8;g!_my!Vt-e z`)_5j!i_aji;3n;FsNjr&%qhaUJ_u!2To)rv)swQ>5l>x5biUaCSKu#_4H0v2Leo> zibaK*Pa+lop6r+w-sSD`U+z}u3Y{x6j$_f{-Cg@aJWe8M7J8p_H!0&{;o;Z3hy9c3B(;#OEPwt%!( z4LP363h2VUH(-R!S($eQ1$VLe*RtE9N*|j2#jl12(z!C?g!nQBau(#)vfu&RY4OHt zjj%U&N-OuaAB}R+Q`STAGKI-EnFJ_Cvi={+=R5~m1d#w$+VA>S$Kkdn^TybosF^i) zPJ1pJFzm--mDFUd58$bZ;xvj8M6Zq0$47d5At%pdL5d3tpJ#0F;{4qqFE1X&0R840 z0eU-!g4p_x4y!c*N&(@r7gNjaO;r?b(FeU&BZkqRMk^xL2d{Fco@^z22#(jgXhVXaV6iIaMEg+qZFmOca zH%lIL)K8DJa!E-`SDw}j;Lbv?yY;;uvQkPn>+?%DM(1B!TdOPGX%C^T>@)r3F$eSR zw{Wu1%#pMSVK*7k?6XaIF)K6HR>11TfB6OX^8A0aIttA8BIq_FuHLQ0xMH^9xvjHT z(#MMq%~9{XcjXJiZ*uNnrzFJ@YhNw)BLGDNoGEf}HLt{8N+NJ?#;iIEX1Ri*?!D`l zP=~F#Z{1w;c>ZekdeBRatK?b2D^j1+`^N_ZnNe6kwu}(t6X@J0+Mro5=GbF5`D!oF zEnxDK8x*#muOHBFH(GfV1-VYHOlm8&-#%aWS*w(Of%$TKrFNts z3y_7%!XNB5PLm^^ui#alc`W@*>^h!6lqty^0cS4<<98po(01r9Shn<`bzL;a& zBAh2{x2b$YdBxgaxnY>P+?2>W`Y5U>@S8@%Y7ySF3-9K2s3#Rrd@Rc3N{E5i#i*!Q z^C^1-?5I;HYQ-o?j^^NlVf|DLX$P))Kr$_V%5ja=E1R$9(QFl$jE_ zT>t*;$%A>x48+NSkv_v}x^n$)f=rJ?x&JIjg=t^=p%@eL>^IH?O;oAIsb>eH_W~cX zSgEQ(TIkLiP0Tbj%3Z5T%PZj`fW9MIVc(9SBoziHa_WdzBs+3r^=^iq|?gtLzLKMutz<3efar-~(k)_7K z*^dDr7<7+Rzs_IyvM=Z-2q)#fTatbA?W(u(&w(88&aus($#dY3xlPVlbp4VZ@{M89shr0&dCFQ4|!wEgn-+LrXBOBz;br}!#45@4Skj9Ydsfqc?h)VncL z7>{}1)4kNzUDhYKb9~#9`pKhDob!*X`*gg#AwBrv{AD~e`0Hk#%3(KW=jSrcz4=S{ zqKTw~u`E2!heTtQyoKki*RFt*tQ7?Mdb<@?HllnG#7LDsv{bNCX<17{mlO`cv)ABJ zcTwzqsda$bo>i%{s7CGWx@(&sUVrP0n~AUR2$2tHy2l$pspGhpj91105DJ4o{T8U} zMg!?O78AXnaQ`6E)g7xZBMq*t(QNi4kIh}~&E1?%Aj)XucVs?-@_;|){QrLWCBSxi z{0qZfs_KIL8#zRW0zd}3s~k5)P#!vB@uLT8bT>f7ENU-cH@HT=fj#9N`}>BkHdFBY z@a6KzMPR|YZ0N(?Wx`u6^E(U#NS<-P`R>cBICi9ztgWpnU0@db&=RB+&#S6PoHjsBbcYOd*ke(DUEg3c9auqh>&fj@Mj09nu4io2R=$DB%H`aw z|E8UG>Z-N$9#`I#;#)P#=keYMZwHR|b9KC3OBH926hAy@nkzJ&6MnI)?kPWx8olfs zdv=wKB|Iee$zF@GCVf;>&KfLWi6*>Xy3O7Xxl2a5?|As)BoJ;T@cwgur$qWQ_~sfB=y@1YO~+DC#hgd zh`h5#UL|#+nG=iL1a-H36<}P{8+$B z@GsQel)4cg{pV?f(p?4t+lA<>tNXVU=C)Nm|5ku=^&KA{{&Bc0x1-WCvXq_LRdC(! zd2gU$NwUQVd2v;}wxR&zYi*yFt?xGKW8k>$!U*Gl)C0VTKeXKoFY6gOIWb1TwO0Vp zqK2W=8x7}T!_0NkUfP~m8ptpZ#IbKF9>4KS$&q!oTf@g}TCq5T*_j=iR@R_B zpyF(7?oswZuMj|f0Nr7RH8RYsW)~FJdhZ7`(UZPa3b*dBTgh&>9Wv$JE%#wwSBCKr zD~Mh5-6Bbt8q2hy%#Y#seS$@RcQq;Me;-(qwg8%B73Y9@;er@7?(?62!mwsJ^@Osk zPur<$KFJv4y03Z6MXIikV{FFGj@?)B0T^+?qZMuo{tCxD9I{DoVtqbb`x~Nb{h-rf zf;GZ$(bl_tIM(AkW;--kv*7X8sLy=G!*>ydC)Ex2)X_Dbz{xZ(zwo!I8Y&zf?!CU& zrDPL;5P~te9y~c<4owTqBCU-_b8WEre{a28XXw9v6-JKdQD#43{w99k?WX2{f4vfB zWvzd7sDHOi_sW{#dvO;=+!QdNJ{7QXzzcO{U+<{?o#L(^rG{dwEOr%rZYmRZo8`xb zODga7IbcYg=T>T~@;2s;&3w!TDit&ha!e~&y`=DSbuUC%X^9BFgoi5+Y+%ApN?(?X z9wc@-v;MoOf$h_(Do4>pt`X_S@Aw=$pDVNxiKb>&B3)13T-0~Sn0=XTm~+|3Bz(5^ z_7w9P;H@4n5iBrGO&;C!tyMJL811u~HFmn5S2%R{#>^3BVMO`KF9lfn3Awl1&C5Cw zh?~z2QFcJcjT;Z3{CpTvsDXJlez?V6Qp&)Fks1ZE_4;aSOE(l|3c+EZ>y5<`8i>@A z!tcZ^TE(5ijde?%$5cyt3bs;f3k&&dXVWnE`_Sc8L{-6+p?Ycbhf`094oCMspMI;_ z^3T1!6Z2O`wA|73Zanp1{mHug%Gh{-r;9o;!TIl^SrR;+@v7mQX_acY8td#QR$~Gy zi|W*3f4*0^$%jKH0jaIOJ1-%Z#cZV7l$3y~#$}ie&p-Py2!{h}q}*_?C zj>Lywl`%68t8gVNpqK0e885X4-e#zQS{j?34Z( z@X+B&aoBaT;>DfTSEg|xX_l5gQ>BUv71icx6O+6T_Aa{nj~hl1vrZuR^GH(X6G9HM zOae$IF#$Vvo#k(r51$atGtgK9;t4+3;Vz@hjFCnleNbF|H{|4myh+Fu7e*q<`rTXn zWEgEYsZ_`NFUYV)9f=ZZuv6gy<8?hR-Zdd{4+ zlf8+Mc8Jt#cPD;xU|RJ|L1fyG)u_?upQku6lFy&#p33*w+O!y6^)E9|2P%F&j@Gxl z-BQpF4vCK*d$32GS&};cy&7}3ftTXdDF4$bR zWair<&-C-ZT?;si9OL+EM3SW0i5)zAbN9*#l`aVpLBM>WmcaQn&?8OJ?Tdu(5U`t# ngj1B;0ZAPgtgYwA_o=7073$h%CF_7qNvSTLGt?{5u?hQMR$S#% literal 0 HcmV?d00001 From 76af24173582742e34143475ca4ed1fa0a7b1d5d Mon Sep 17 00:00:00 2001 From: Luiz Aoqui Date: Sat, 19 Mar 2022 13:21:37 -0400 Subject: [PATCH 07/12] docs: manter infra e fonte do bot no mesmo repo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Usar o mesmo repo para infra e código-fonte permite a melhor integração de deploy e controle sobre a zona de impacto de mudanças. --- rfcs/RFC-001/RFC-001.md | 168 ++++++++++++++++++++++++++-------------- 1 file changed, 110 insertions(+), 58 deletions(-) diff --git a/rfcs/RFC-001/RFC-001.md b/rfcs/RFC-001/RFC-001.md index 72bdb9c..68c03d8 100644 --- a/rfcs/RFC-001/RFC-001.md +++ b/rfcs/RFC-001/RFC-001.md @@ -62,8 +62,8 @@ for atualizada. O slot `prod` será atualizado quando uma tag nova de release for criada. A configuração e infraestrutura criada para o bot de Agenda poderá ser -reutilizada para outros bots, reduzindo o atrito e o trabalho necessário para -criar novos bots. +reutilizada para outros bots, criando uma plataforma de bots e reduzindo o +atrito e o trabalho necessário para criar novos bots. A arquitetura final da infraestrutura ficará assim: @@ -89,17 +89,37 @@ e criar os recursos e serviços necessários na conta Azure que será utilizada. Essa configuração será feita com o Terraform seguindo a prática de infraestrutura-como-código. O código Terraform será organizado em um estrutura -multi-repo, composta de um repositório raiz que irá consumir módulos -armazenados em outros repositórios. +multi-repo, composta de um repositório raiz que irá criar a infraestrutura base +da plataforma, um módulo para criar e gerenciar as instâncias de App Service e +slots na Azure e configurações específicas de cada bot que ficarão no mesmo +repositório que o código-fonte do bot e irá consumir o módulo e a plataforma. -### iac-mentoria-bots +### iac-plataforma-bots -`iac-mentoria-bots` será o repositório raiz que irá definir a infraestrutura -base e consumir os outros módulos. Nesse repositório será criado o resource -group ([`azurerm_resource_group`][azurerm_resource_group]) onde os outro -recursos dos bots serão definidos e os App Service Plans -([`azurerm_app_service_plan`][azurerm_app_service_plan]) necessários para -rodar os bots. +`iac-plataforma-bots` será o repositório raiz que irá definir a infraestrutura +base da plataforma de bots. Nesse repositório será criado o resource group +([`azurerm_resource_group`][azurerm_resource_group]) onde os outro recursos dos +bots serão definidos e os App Service Plans +([`azurerm_app_service_plan`][azurerm_app_service_plan]) necessários para rodar +os bots. + +#### Exemplos + +`main.tf` + +```hcl +resource "azure_resource_group" "bots" { + name = "plataforma-bots" + location = "Brazil South" +} + +resource "azurerm_app_service_plan" "bots" { + name = "bots" + location = azurerm_resource_group.bots.location + resource_group_name = azurerm_resource_group.bots.name + # ... +} +``` ### iac-modulo-app-service-docker @@ -116,42 +136,9 @@ Adotar o Docker como opção padrão de execução permite simplificar a interfa a implementação do módulo, uma vez que não é necessário suportar configurações variadas de `site_config`. -### Exemplos +#### Exemplos -`iac-mentoria-bots/main.tf` - -```hcl -resource "azure_resource_group" "bots" { - name = "mentoria-bots" - location = "Brazil South" -} - -module "bot_agenda" { - source = "github.com/mentoriaiac/iac-modulo-app-service-docker.git?ref=v0.1.0" - - name = "bot-agenda" - image = "metoria-iac/bot-agenda:1.0.0" - resource_group_name = azure_resource_group.bots.name - app_service_plan_id = azurerm_app_service_plan.bots.id - - app_settings = { - "KEY" = "value" - } -} -``` - -`iac-mentoria-bots/app_service_plans.tf` - -```hcl -resource "azurerm_app_service_plan" "bots" { - name = "bots" - location = azurerm_resource_group.bots.location - resource_group_name = azurerm_resource_group.bots.name - # ... -} -``` - -`iac-modulo-app-service-docker/variables.tf` +`variables.tf` ```hcl variable "name" {} @@ -159,11 +146,10 @@ variable "image" {} variable "app_settings" {} variable "resource_group_name" {} variable "app_service_plan_id" {} - # ... ``` -`iac-modulo-app-service-docker/main.tf` +`main.tf` ```hcl data "azurerm_resource_group" "rg" { @@ -181,7 +167,6 @@ resource "azurerm_app_service" "bot" { } app_settings = var.app_settings - # ... } @@ -191,7 +176,6 @@ resource "azurerm_app_service_slot" "prod" { app_service_plan_id = var.app_service_plan_id location = data.azurerm_resource_group.rg.location resource_group_name = data.azurerm_resource_group.rg.name - # ... } @@ -201,12 +185,11 @@ resource "azurerm_app_service_slot" "staging" { app_service_plan_id = var.app_service_plan_id location = data.azurerm_resource_group.rg.location resource_group_name = data.azurerm_resource_group.rg.name - # ... } ``` -`iac-modulo-app-service-docker/outputs.tf` +`outputs.tf` ```hcl output "prod_slot_id" { @@ -216,20 +199,73 @@ output "prod_slot_id" { output "staging_slot_id" { value = azurerm_app_service_slot.staging.id } - # ... ``` +### Repositório dos bots + +Dentro de cada repositório dos bots será criada uma pasta chamada `infra` onde +ficará a configuração Terraform do bot. Essa configuração consumirá o módulo +`iac-modulo-app-service-docker` e recursos criados na `iac-plataforma-bots`. + +O deploy da infraestrutura do bot estará associado ao deploy do código-fonte. A +cada versão nova do bot, a variável da tag da imagem Docker será atualizada +para usar a nova versão. O uso do formato JSON para definir o valor da variável +facilita a manipulação desse valor de forma programática. + +#### Exemplos + +`infra/main.tf` + +```hcl +# Referências para a infraestrutura providenciada pela plataforma. +data "azurerm_resource_group" "plataforma_bots" { + name = "plataforma-bots" +} + +data "azurerm_app_service_plan" "bots" { + name = "bots" + resource_group_name = data.azurerm_resource_group.plataforma_bots.name +} + +module "bot_agenda" { + source = "github.com/mentoriaiac/iac-modulo-app-service-docker.git?ref=v0.1.0" + + name = "bot-agenda" + image = "metoria-iac/bot-agenda:${var.image_tag}" + resource_group_name = data.azurerm_resource_group.plataforma_bots.name + app_service_plan_id = data.azurerm_app_service_plan.bots.id + + app_settings = { + "KEY" = "value" + } +} +``` + +`infra/variables.tf` + +```hcl +variable image_tag {} +``` + +`infra/terraform.tfvars.json` + +```json +{ + "image_tag": "v1.0.0" +} +``` + ### Pipeline to Terraform Os repositórios Terraform irão usar GitHub Actions para executar pipelines de CI/CD. -No repositório raiz `iac-mentoria-bots`, a parte de CI acontecerá a cada commit -e PR. Serão executadas tarefas básicas de validação, como `terraform fmt` e -`terraform validate`. A parte de CD acontecerá na branch `main` toda a vez que -uma tag de release for criada, e `terraform plan` e `terraform apply` serão -executados. +No repositório raiz `iac-plataforma-bots`, a parte de CI acontecerá a cada +commit e PR. Serão executadas tarefas básicas de validação, como +`terraform fmt` e `terraform validate`. A parte de CD acontecerá na branch +`main` toda a vez que uma tag de release for criada, e `terraform plan` e +`terraform apply` serão executados. O repositório do módulo `iac-modulo-app-service-docker` irá executar as mesmas ações de CI, e usará uma configuração de exemplo para validação de CD que será @@ -251,6 +287,22 @@ Definir um único módulo que engloba vários resources que seriam comuns a vár deployments de bots possui mais valor por simplificar como bots são definidos, escondendo os detalhes do App Service, slots etc. +#### Centralizar a infraestrutura dos bots no repositório da plataforma + +A declaração da infraestrutura de cada bot poderia ser definida dentro do +repostirório da plataforma (`iac-plataforma-bots`). Porém, isso criaria uma +lacuna entre o produto e a infraestrutura: modificações no código-fonte da +aplicação teriam que ser propagados de um repositório para outro. + +Manter o código de infraestrutura junto como o código de aplicação permite a +criação de um pipeline completo de ponta a ponta no mesmo repositório. + +Uma outra vantagem é reduzir a zona de impacto de mudanças. Isolando a +configuração específica de cada bot previne com que um projeto afete o outro ou +afete a plataforma em si. Aplicando alterações no nível da aplicação também +reduz o número de vezes que é preciso aplicar alterações na infraestrutura da +plataforma. + ## Pipeline do Bot A parte de CI irá incluir várias etapas. Os primeiros passos serão feitos sobre From 48d274e7762ea136bce08491618ee21f79ce2f4f Mon Sep 17 00:00:00 2001 From: Luiz Aoqui Date: Sat, 19 Mar 2022 14:34:28 -0400 Subject: [PATCH 08/12] =?UTF-8?q?docs:=20atualiza=C3=A7=C3=A3o=20de=20pipe?= =?UTF-8?q?lines=20dos=20bots?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adicionando mais detalhes de como irão ficar os pipelines de deploy dos bots. --- rfcs/RFC-001/RFC-001.md | 108 ++++++++++++++++++++++++++-------------- 1 file changed, 70 insertions(+), 38 deletions(-) diff --git a/rfcs/RFC-001/RFC-001.md b/rfcs/RFC-001/RFC-001.md index 68c03d8..0532d17 100644 --- a/rfcs/RFC-001/RFC-001.md +++ b/rfcs/RFC-001/RFC-001.md @@ -142,8 +142,10 @@ variadas de `site_config`. ```hcl variable "name" {} -variable "image" {} -variable "app_settings" {} +variable "image_prod" {} +variable "image_staging" {} +variable "app_settings_prod" {} +variable "app_settings_staging" {} variable "resource_group_name" {} variable "app_service_plan_id" {} # ... @@ -161,12 +163,6 @@ resource "azurerm_app_service" "bot" { location = data.azurerm_resource_group.rg.location resource_group_name = data.azurerm_resource_group.rg.name app_service_plan_id = var.app_service_plan_id - - site_config { - linux_fx_version = "DOCKER|${var.image}" - } - - app_settings = var.app_settings # ... } @@ -176,6 +172,12 @@ resource "azurerm_app_service_slot" "prod" { app_service_plan_id = var.app_service_plan_id location = data.azurerm_resource_group.rg.location resource_group_name = data.azurerm_resource_group.rg.name + + site_config { + linux_fx_version = "DOCKER|${var.image_prod}" + } + + app_settings = var.app_settings_prod # ... } @@ -185,6 +187,12 @@ resource "azurerm_app_service_slot" "staging" { app_service_plan_id = var.app_service_plan_id location = data.azurerm_resource_group.rg.location resource_group_name = data.azurerm_resource_group.rg.name + + site_config { + linux_fx_version = "DOCKER|${var.image_staging}" + } + + app_settings = var.app_settings_staging # ... } ``` @@ -232,12 +240,20 @@ module "bot_agenda" { source = "github.com/mentoriaiac/iac-modulo-app-service-docker.git?ref=v0.1.0" name = "bot-agenda" - image = "metoria-iac/bot-agenda:${var.image_tag}" resource_group_name = data.azurerm_resource_group.plataforma_bots.name app_service_plan_id = data.azurerm_app_service_plan.bots.id - app_settings = { + image_prod = "metoria-iac/bot-agenda:${var.image_prod_tag}" + image_staging = "metoria-iac/bot-agenda:${var.image_staging_tag}" + + app_settings_prod = { "KEY" = "value" + "DISCORD_TOKEN" = var.discord_token_prod + } + + app_settings_staging = { + "KEY" = "value" + "DISCORD_TOKEN" = var.discord_token_staging } } ``` @@ -245,32 +261,21 @@ module "bot_agenda" { `infra/variables.tf` ```hcl -variable image_tag {} +variable image_prod_tag {} +variable image_staging_tag {} +variable discord_token_prod {} +variable discord_token_staging {} ``` `infra/terraform.tfvars.json` ```json { - "image_tag": "v1.0.0" + "image_prod_tag": "v1.0.0" + "image_staging_tag": "f5956b1" } ``` -### Pipeline to Terraform - -Os repositórios Terraform irão usar GitHub Actions para executar pipelines de -CI/CD. - -No repositório raiz `iac-plataforma-bots`, a parte de CI acontecerá a cada -commit e PR. Serão executadas tarefas básicas de validação, como -`terraform fmt` e `terraform validate`. A parte de CD acontecerá na branch -`main` toda a vez que uma tag de release for criada, e `terraform plan` e -`terraform apply` serão executados. - -O repositório do módulo `iac-modulo-app-service-docker` irá executar as mesmas -ações de CI, e usará uma configuração de exemplo para validação de CD que será -destruída automaticamente. - ### Ideias Abandonadas #### Módulos para resource group, App Service e App Service Plan @@ -303,7 +308,28 @@ afete a plataforma em si. Aplicando alterações no nível da aplicação també reduz o número de vezes que é preciso aplicar alterações na infraestrutura da plataforma. -## Pipeline do Bot +## Pipelines de deploy + +Para transformar código em infraestrutura serão criados diversos pipelines em +cada um dos repositórios para automatizar o processo. As etapas presentes em +cada pipeline irão depender do tipo de código no repositório. + +### Pipeline Terraform + +Os repositórios Terraform irão usar GitHub Actions para executar pipelines de +CI/CD. + +No repositório raiz `iac-plataforma-bots`, a parte de CI acontecerá a cada +commit e PR. Serão executadas tarefas básicas de validação, como +`terraform fmt` e `terraform validate`. A parte de CD acontecerá na branch +`main` toda a vez que uma tag de release for criada, e `terraform plan` e +`terraform apply` serão executados. + +O repositório do módulo `iac-modulo-app-service-docker` irá executar as mesmas +ações de CI, e usará uma configuração de exemplo para validação de CD que será +destruída automaticamente. + +### Pipeline do Bot A parte de CI irá incluir várias etapas. Os primeiros passos serão feitos sobre o código-fonte, como linters, testes estáticos, testes unitários, etc. e irão @@ -312,16 +338,17 @@ rodar a cada commit e PR. Quando o PR for aprovado e a branch for integrada à `main`, o pipeline de CD para staging será iniciado. Esse pipeline irá construir a imagem Docker e publicá-la no Docker Hub usando a versão curta (7 caracteres) do SHA do commit. -Depois de publicada, a imagem será usada para atualizar o slot de staging na -Azure. - -Quando uma tag de release for criada, o pipeline de CD para produção será -executado. A imagem Docker será criada e publicada no Docker Hub usando a tag -git como tag da imagem. O slot de produção na Azure será então atualizado para -rodar a nova imagem. - -Os pipelines de CD irão usar a Action `azure/webapps-deploy@v2` para atualizar -o App Service Slot de cada ambiente. +Depois de publicada, o arquivo de variáveis será editado para atualizar a +variável `image_staging_tag` para o valor da nova tag. Essa modificação irá ser +integrada de volta ao repositório através de um commit e push para manter o +registro de alteração da infraestrutura. A configuração do Terraform será então +aplicada para atualizar o slot de staging na Azure. + +O pipeline de produção será parecido, mas irá ser iniciado a partir da criação +de uma tag de release. A imagem Docker será criada e publicada no Docker Hub +usando a tag git como tag da imagem. A variável `image_prod_tag` será +atualizada para esse novo valor e a aplicando a configuração do Terraform irá +atualizar o slot de produção na Azure. ## Configuração de Slots @@ -329,6 +356,11 @@ Cada slot do bot irá apontar para um servidor Discord diferente a fim de nos permitir validar mudanças no código sem afetar o servidor Discord oficial da Mentoria. +Os valores dos tokens de autenticação serão definidos como uma variável de +ambiente em cada slot do App Service. Esses valores serão armazenados no +repositório como [segredos encriptados][gh_action_secrets] para que o pipeline +consiga consumi-los. + [app_service]: https://azure.microsoft.com/en-us/services/app-service [app_service_plan]: https://docs.microsoft.com/en-us/azure/app-service/overview-hosting-plans [azure_gh_action]: https://github.com/marketplace/actions/azure-webapp From 61d75576dbc91e7377a6722d53a2649e72536662 Mon Sep 17 00:00:00 2001 From: Luiz Aoqui Date: Sat, 19 Mar 2022 14:41:27 -0400 Subject: [PATCH 09/12] docs: arrumando link quebrado --- rfcs/RFC-001/RFC-001.md | 1 + 1 file changed, 1 insertion(+) diff --git a/rfcs/RFC-001/RFC-001.md b/rfcs/RFC-001/RFC-001.md index 0532d17..610a304 100644 --- a/rfcs/RFC-001/RFC-001.md +++ b/rfcs/RFC-001/RFC-001.md @@ -372,6 +372,7 @@ consiga consumi-los. [bot_repo]: https://github.com/mentoriaiac/bot-discord-mentoria-agenda [discord]: https://discordapp.com [docker_hub]: https://hub.docker.com/ +[gh_action_secrets]: https://docs.github.com/pt/actions/security-guides/encrypted-secrets [heroku]: https://heroku.com/ [heroku_gh_action]: https://github.com/marketplace/actions/deploy-to-heroku [slots]: https://docs.microsoft.com/en-us/azure/app-service/deploy-staging-slots From 9d7750cd68c51510be83988bbbcd64f238516ad1 Mon Sep 17 00:00:00 2001 From: Luiz Aoqui Date: Sat, 19 Mar 2022 14:50:01 -0400 Subject: [PATCH 10/12] docs: adicionando fonte para diagrama da azure --- rfcs/RFC-001/src/azure.drawio | 1 + 1 file changed, 1 insertion(+) create mode 100644 rfcs/RFC-001/src/azure.drawio diff --git a/rfcs/RFC-001/src/azure.drawio b/rfcs/RFC-001/src/azure.drawio new file mode 100644 index 0000000..8679286 --- /dev/null +++ b/rfcs/RFC-001/src/azure.drawio @@ -0,0 +1 @@ +7VnbcpswEP0aHp3hYmH7MbZzeWhnOnWneezIIIMaITFC2E6+visQNgTIpakbJomdB3QkraQ9Z3dlYnmLZH8lcRp/FSFhlmuHe8tbWq7rjF3X0n92eFciEzQtgUjS0Aw6Ait6TwxoGzSnIckaA5UQTNG0CQaCcxKoBoalFLvmsI1gzVVTHJEWsAowa6M3NFRxiU6RfcSvCY3iamXHNj0JrgYbIItxKHY1yLuwvIUUQpVPyX5BmHZe5Zdy3mVP72FjknD1nAn7n760F+dfrr08G+HfwU3K2GhWWtlilpsDfyeZyGVAAL2SIk/N5tVd5ZGN4GplmrblzbdEKgoOO2c04oApkQKKTSuA3REJQJbigPLoh+5djuD08w1lbCGYgN4lFxzszWFBHpLQWN7FVJEVzNNL7UBfgMUqYdBytEUlxS2pLFiuN3P099BT8aXXCnEWF3Z1N2hFYcr1tkybMZxmdH04E9mnmFfbkCTIZUa3BFxT6lOjxnFweLLvZcQ58AwBQkRClLyDIWbC2CjDhIY7Me3dUWh+hcU1kXmVpLARd3QwfeQfHowEXiAHryWH8xQUYK+I3NJCE98Y5p+SeIUk+sOwVyduUyceaskETTtk4o7RiWTitnXS0kR0TB4dHjHpGq+r4fYzPeU97qnpA1f5LVcdvFJ3lTM9VUQ57QzbctVTor4lKoiNxuoRAvreIP3tUr5ffPQMkHYNLz+vUqvbQ8LbebmST83La6FGbVfjLC1vCRu618Ff93MqKFfFztDcQsuujEWT4rpQ+NTcVRz3iC9pEsH+GV3rU2QBhox5CRn0l8mf2Vm2jR4y+u95MGZmkzPUiAanXV9QR95AJ2PJ6WZplEoRjjIGN6GPzJfXzF2zt2bL7WErUziCsv1JmOMPizFnMtSyPLiq7L+/qlyyP6Sq7LSrMkQxD/H7zhp9RAy1LDvjjmDwmdK/kegWHiP9WDJXL9XlEFiyNupDMzuwAu6gnvD7QBX8CcYGV8GfLkv/kR58n0uiT4SBn8zw06Tr1wXfUil4AvYr6v6ercdfg6BJkywXuW22/A62/BOx1b4gDyGYqhe4xdvbE1Piowc3O/t5ATR+eQBB8/iuvOir/cfBu/gD \ No newline at end of file From 584752a8749784fe82efe56859f0dafadcf77fa6 Mon Sep 17 00:00:00 2001 From: Luiz Aoqui Date: Sun, 20 Mar 2022 14:24:55 -0400 Subject: [PATCH 11/12] =?UTF-8?q?chore:=20atualiza=20template=20para=20o?= =?UTF-8?q?=20novo=20cabe=C3=A7alho?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rfcs/RFC-001/RFC-001.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rfcs/RFC-001/RFC-001.md b/rfcs/RFC-001/RFC-001.md index 610a304..e0bb280 100644 --- a/rfcs/RFC-001/RFC-001.md +++ b/rfcs/RFC-001/RFC-001.md @@ -1,8 +1,8 @@ # [RFC-001] Migração do bot da agenda para a Azure -| Data | Responsáveis | Colaborações | Aprovações | -| --------------------- | ------------ | -------------------------------------------------------- | ---------- | -| 21 de fevereiro, 2022 | @Rehzende | @DiogoMajela @selton00 @afernandescneto @phconte @lgfa29 | | +| Data | Responsáveis | Colaborações | +| --------------------- | ----------------- | ------------------------------------------------------- | +| 21 de fevereiro, 2022 | @Rehzende @lgfa29 | @DiogoMajela @selton00 @afernandescneto @phconte @gomex | **Sumário:** Migração do bot de agenda do Discord do Heroku para a Azure. From 173aab68d9a20cf5858189e4e7a7d321f9d3aa73 Mon Sep 17 00:00:00 2001 From: Luiz Aoqui Date: Sun, 10 Apr 2022 12:52:53 -0400 Subject: [PATCH 12/12] docs: atualiza recursos Azure para v3 --- rfcs/RFC-001/RFC-001.md | 46 +++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/rfcs/RFC-001/RFC-001.md b/rfcs/RFC-001/RFC-001.md index e0bb280..f2ce5b9 100644 --- a/rfcs/RFC-001/RFC-001.md +++ b/rfcs/RFC-001/RFC-001.md @@ -100,8 +100,8 @@ repositório que o código-fonte do bot e irá consumir o módulo e a plataforma base da plataforma de bots. Nesse repositório será criado o resource group ([`azurerm_resource_group`][azurerm_resource_group]) onde os outro recursos dos bots serão definidos e os App Service Plans -([`azurerm_app_service_plan`][azurerm_app_service_plan]) necessários para rodar -os bots. +([`azurerm_service_plan`][azurerm_service_plan]) necessários para rodar os +bots. #### Exemplos @@ -113,21 +113,23 @@ resource "azure_resource_group" "bots" { location = "Brazil South" } -resource "azurerm_app_service_plan" "bots" { +resource "azurerm_service_plan" "bots" { name = "bots" location = azurerm_resource_group.bots.location resource_group_name = azurerm_resource_group.bots.name + os_type = "Linux" # ... } ``` -### iac-modulo-app-service-docker +### iac-modulo-linux-app-service-docker -`iac-modulo-app-service-docker` será um módulo Terraform quer irá definir a -estrutura padrão de um app que rode um container Docker. O módulo será composto -de um App Service ([`azurerm_app_service`][azurerm_app_service]) e dois App -Service Slots ([`azurerm_app_service_slot`][azurerm_app_service_slot]), um para -staging e outro para produção. +`iac-modulo-linux-app-service-docker` será um módulo Terraform quer irá definir +a estrutura padrão de um app que rode um container Docker em um host Linux. O +módulo será composto de um App Service Linux +([`azurerm_linux_web_app`][azurerm_linux_web_app]) e dois App Service Slots +([`azurerm_linux_web_app_slot`][azurerm_linux_web_app_slot]), um para staging +e outro para produção. As variáveis de entrada do módulo irão definir os detalhes do App Service, como nome, a imagem Docker do bot, `app_settings`, `tags` etc. @@ -158,17 +160,17 @@ data "azurerm_resource_group" "rg" { name = var.resource_group_name } -resource "azurerm_app_service" "bot" { +resource "azurerm_linux_web_app" "bot" { name = var.name location = data.azurerm_resource_group.rg.location resource_group_name = data.azurerm_resource_group.rg.name - app_service_plan_id = var.app_service_plan_id + service_plan_id = var.service_plan_id # ... } -resource "azurerm_app_service_slot" "prod" { +resource "azurerm_linux_web_app_slot" "prod" { name = "${var.name}-prod" - app_service_name = azurerm_app_service.bot.name + app_service_name = azurerm_linux_web_app.bot.name app_service_plan_id = var.app_service_plan_id location = data.azurerm_resource_group.rg.location resource_group_name = data.azurerm_resource_group.rg.name @@ -181,9 +183,9 @@ resource "azurerm_app_service_slot" "prod" { # ... } -resource "azurerm_app_service_slot" "staging" { +resource "azurerm_linux_web_app_slot" "staging" { name = "${var.name}-staging" - app_service_name = azurerm_app_service.bot.name + app_service_name = azurerm_linux_web_app.bot.name app_service_plan_id = var.app_service_plan_id location = data.azurerm_resource_group.rg.location resource_group_name = data.azurerm_resource_group.rg.name @@ -201,11 +203,11 @@ resource "azurerm_app_service_slot" "staging" { ```hcl output "prod_slot_id" { - value = azurerm_app_service_slot.prod.id + value = azurerm_linux_web_app_slot.prod.id } output "staging_slot_id" { - value = azurerm_app_service_slot.staging.id + value = azurerm_linux_web_app_slot.staging.id } # ... ``` @@ -231,7 +233,7 @@ data "azurerm_resource_group" "plataforma_bots" { name = "plataforma-bots" } -data "azurerm_app_service_plan" "bots" { +data "azurerm_service_plan" "bots" { name = "bots" resource_group_name = data.azurerm_resource_group.plataforma_bots.name } @@ -241,7 +243,7 @@ module "bot_agenda" { name = "bot-agenda" resource_group_name = data.azurerm_resource_group.plataforma_bots.name - app_service_plan_id = data.azurerm_app_service_plan.bots.id + app_service_plan_id = data.azurerm_service_plan.bots.id image_prod = "metoria-iac/bot-agenda:${var.image_prod_tag}" image_staging = "metoria-iac/bot-agenda:${var.image_staging_tag}" @@ -365,9 +367,9 @@ consiga consumi-los. [app_service_plan]: https://docs.microsoft.com/en-us/azure/app-service/overview-hosting-plans [azure_gh_action]: https://github.com/marketplace/actions/azure-webapp [azure_rg]: https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/manage-resource-groups-portal -[azurerm_app_service]: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/app_service -[azurerm_app_service_plan]: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/app_service_plan -[azurerm_app_service_slot]: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/app_service_slot +[azurerm_linux_web_app]: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_web_app +[azurerm_service_plan]: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/service_plan +[azurerm_linux_web_app_slot]: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_web_app_slot [azurerm_resource_group]: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group [bot_repo]: https://github.com/mentoriaiac/bot-discord-mentoria-agenda [discord]: https://discordapp.com