1
0
Форкнуть 0

Merge branch main into feat/urlupdates

This commit is contained in:
Armen Kaleshian 2023-03-22 03:54:56 -04:00
Родитель 1f64634f06 21204bd71c
Коммит ad6a0e91f6
113 изменённых файлов: 1061 добавлений и 101 удалений

567
.github/workflows/deploy.yml поставляемый Normal file
Просмотреть файл

@ -0,0 +1,567 @@
name: Deploy to Azure Spring Apps using passwordless connections
on: [push]
permissions:
id-token: write
contents: read
env:
# === Deploy Firewall ===:
SHOULD_DEPLOY_FIREWALL: false
# === Destroy All ==
SHOULD_DESTROY: false
# === Spring Apps Service ===:
SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
JUMP_BOX_USERNAME: lzadmin
JUMP_BOX_PASSWORD: ${{ secrets.JUMP_BOX_PASSWORD }}
# Specify the Object ID for the "Azure Spring Apps Resource Provider" service principal in the customer's Azure AD Tenant
# Use this command to obtain:
# az ad sp show --id e8de9221-a19c-4c81-b814-fd37c6caf9d2 --query id --output tsv
SRINGAPPS_SPN_OBJECT_ID: 1b7a460f-dc1f-42d5-a4d5-0505272dfd54
# === Pet Clinic Required Settings ===:
MYSQL_ADMIN_USERNAME: sqlAdmin
MYSQL_ADMIN_PASSWORD: ${{ secrets.MYSQL_ADMIN_PASSWORD }}
# ==== Terraform Backend ===:
TFSTATE_RG: cloud-shell-storage-southcentralus
STORAGEACCOUNTNAME: cs7100320022eb75670
CONTAINERNAME: tfstateasa
# ==== Terraform Azure Login ===:
ARM_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
ARM_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
ARM_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
ARM_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
# === Baseline Parameters ====:
REGION: eastus
NAME_PREFIX: springlza
ENVIRONMENT: dev
# ==== APPS ====:
API_GATEWAY: api-gateway
ADMIN_SERVER: admin-server
CUSTOMERS_SERVICE: customers-service
VETS_SERVICE: vets-service
VISITS_SERVICE: visits-service
# ==== JARS ====:
API_GATEWAY_JAR: spring-petclinic-api-gateway/target/spring-petclinic-api-gateway-3.0.1.jar
ADMIN_SERVER_JAR: spring-petclinic-admin-server/target/spring-petclinic-admin-server-3.0.1.jar
CUSTOMERS_SERVICE_JAR: spring-petclinic-customers-service/target/spring-petclinic-customers-service-3.0.1.jar
VETS_SERVICE_JAR: spring-petclinic-vets-service/target/spring-petclinic-vets-service-3.0.1.jar
VISITS_SERVICE_JAR: spring-petclinic-visits-service/target/spring-petclinic-visits-service-3.0.1.jar
jobs:
conditions:
runs-on: ubuntu-latest
outputs:
deploy_firewall: "${{ env.SHOULD_DEPLOY_FIREWALL }}"
destroy: "${{ env.SHOULD_DESTROY }}"
steps:
- name: echo
run: |
echo ${{ env.SHOULD_DEPLOY_FIREWALL }} \
echo ${{ env.SHOULD_DESTROY }}
deploy_hub_network:
name: Deploy 02 Hub Network
needs: conditions
runs-on: ubuntu-latest
defaults:
run:
working-directory: Scenarios/ASA-Secure-Baseline/Terraform/02-Hub-Network
steps:
- name: Checkout this repository
uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2.0.3
with:
terraform_wrapper: false
- name: Terraform Init
run: |
terraform init \
-backend-config="resource_group_name=${{ env.TFSTATE_RG }}" \
-backend-config="storage_account_name=${{ env.STORAGEACCOUNTNAME }}" \
-backend-config="container_name=${{ env.CONTAINERNAME }}"
- name: Terraform Plan
run: |
terraform plan -out my.plan \
-var="state_sa_rg=${{ env.TFSTATE_RG }}" \
-var="state_sa_name=${{ env.STORAGEACCOUNTNAME }}" \
-var="state_sa_container_name=${{ env.CONTAINERNAME }}" \
-var="location=${{ env.REGION }}" \
-var="name_prefix=${{ env.NAME_PREFIX }}" \
-var="environment=${{ env.ENVIRONMENT }}" \
-var="SRINGAPPS_SPN_OBJECT_ID=${{ env.SRINGAPPS_SPN_OBJECT_ID }}"
- name: Terraform Apply
run: terraform apply my.plan
deploy_lz_network:
needs: [deploy_hub_network, conditions]
name: Deploy 03 LZ Network
runs-on: ubuntu-latest
defaults:
run:
working-directory: Scenarios/ASA-Secure-Baseline/Terraform/03-LZ-Network
steps:
- name: Checkout this repository
uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2.0.3
with:
terraform_wrapper: false
- name: Terraform Init
run: |
terraform init \
-backend-config="resource_group_name=${{ env.TFSTATE_RG }}" \
-backend-config="storage_account_name=${{ env.STORAGEACCOUNTNAME }}" \
-backend-config="container_name=${{ env.CONTAINERNAME }}"
- name: Terraform Plan
run: |
terraform plan -out my.plan \
-var="state_sa_rg=${{ env.TFSTATE_RG }}" \
-var="state_sa_name=${{ env.STORAGEACCOUNTNAME }}" \
-var="state_sa_container_name=${{ env.CONTAINERNAME }}" \
-var="location=${{ env.REGION }}" \
-var="name_prefix=${{ env.NAME_PREFIX }}" \
-var="environment=${{ env.ENVIRONMENT }}" \
-var="SRINGAPPS_SPN_OBJECT_ID=${{ env.SRINGAPPS_SPN_OBJECT_ID }}"
- name: Terraform Apply
run: terraform apply my.plan
deploy_lz_shared:
needs: [deploy_lz_network, conditions]
name: Deploy 04 LZ Shared Resources
runs-on: ubuntu-latest
defaults:
run:
working-directory: Scenarios/ASA-Secure-Baseline/Terraform/04-LZ-SharedResources
steps:
- name: Checkout this repository
uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2.0.3
with:
terraform_wrapper: false
- name: Terraform Init
run: |
terraform init \
-backend-config="resource_group_name=${{ env.TFSTATE_RG }}" \
-backend-config="storage_account_name=${{ env.STORAGEACCOUNTNAME }}" \
-backend-config="container_name=${{ env.CONTAINERNAME }}"
- name: Terraform Plan
run: |
terraform plan -out my.plan \
-var="state_sa_rg=${{ env.TFSTATE_RG }}" \
-var="state_sa_name=${{ env.STORAGEACCOUNTNAME }}" \
-var="state_sa_container_name=${{ env.CONTAINERNAME }}" \
-var="location=${{ env.REGION }}" \
-var="name_prefix=${{ env.NAME_PREFIX }}" \
-var="environment=${{ env.ENVIRONMENT }}" \
-var="SRINGAPPS_SPN_OBJECT_ID=${{ env.SRINGAPPS_SPN_OBJECT_ID }}" \
-var="jump_host_admin_username=${{ env.JUMP_BOX_USERNAME }}" \
-var="jump_host_password=${{ env.JUMP_BOX_PASSWORD}}"
- name: Terraform Apply
run: terraform apply my.plan
deploy_hub_firewall:
needs: [deploy_hub_network, deploy_lz_shared, conditions]
name: Deploy 05 Hub Firewall
runs-on: ubuntu-latest
defaults:
run:
working-directory: Scenarios/ASA-Secure-Baseline/Terraform/05-Hub-AzureFirewall
steps:
- name: Checkout this repository
uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2.0.3
with:
terraform_wrapper: false
- name: Terraform Init
run: |
terraform init \
-backend-config="resource_group_name=${{ env.TFSTATE_RG }}" \
-backend-config="storage_account_name=${{ env.STORAGEACCOUNTNAME }}" \
-backend-config="container_name=${{ env.CONTAINERNAME }}"
- name: Terraform Plan
run: |
terraform plan -out my.plan \
-var="state_sa_rg=${{ env.TFSTATE_RG }}" \
-var="state_sa_name=${{ env.STORAGEACCOUNTNAME }}" \
-var="state_sa_container_name=${{ env.CONTAINERNAME }}" \
-var="location=${{ env.REGION }}" \
-var="name_prefix=${{ env.NAME_PREFIX }}" \
-var="environment=${{ env.ENVIRONMENT }}" \
-var="SRINGAPPS_SPN_OBJECT_ID=${{ env.SRINGAPPS_SPN_OBJECT_ID }}"
- name: Terraform Apply
if: needs.conditions.outputs.deploy_firewall == 'true'
run: terraform apply my.plan
deploy_lz_standard:
needs:
[deploy_hub_network, deploy_lz_shared, deploy_hub_firewall, conditions]
name: Deploy 06 LZ Spring Apps Standard
runs-on: ubuntu-latest
outputs:
spring_apps_service_name: ${{ steps.output.outputs.spring_apps_service_name }}
defaults:
run:
working-directory: Scenarios/ASA-Secure-Baseline/Terraform/06-LZ-SpringApps-Standard
steps:
- name: Checkout this repository
uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2.0.3
with:
terraform_wrapper: false
- name: Terraform Init
run: |
terraform init \
-backend-config="resource_group_name=${{ env.TFSTATE_RG }}" \
-backend-config="storage_account_name=${{ env.STORAGEACCOUNTNAME }}" \
-backend-config="container_name=${{ env.CONTAINERNAME }}"
- name: Terraform Plan
run: |
terraform plan -out my.plan \
-var="state_sa_rg=${{ env.TFSTATE_RG }}" \
-var="state_sa_name=${{ env.STORAGEACCOUNTNAME }}" \
-var="state_sa_container_name=${{ env.CONTAINERNAME }}" \
-var="location=${{ env.REGION }}" \
-var="name_prefix=${{ env.NAME_PREFIX }}" \
-var="environment=${{ env.ENVIRONMENT }}" \
-var="SRINGAPPS_SPN_OBJECT_ID=${{ env.SRINGAPPS_SPN_OBJECT_ID }}"
- name: Terraform Apply
run: terraform apply my.plan
- name: Terraform Output
id: output
run: echo spring_apps_service_name=$(terraform output -raw spring_apps_service_name) >> $GITHUB_OUTPUT
deploy_pet_clinic_infra:
name: Deploy Pet Clinic Infrastructure
needs: [deploy_lz_standard, deploy_lz_shared, conditions]
runs-on: ubuntu-latest
defaults:
run:
working-directory: Scenarios/sample-apps/petclinic/terraform
steps:
- name: Checkout this repository
uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2.0.3
with:
terraform_wrapper: false
- name: Terraform Init
run: |
terraform init \
-backend-config="resource_group_name=${{ env.TFSTATE_RG }}" \
-backend-config="storage_account_name=${{ env.STORAGEACCOUNTNAME }}" \
-backend-config="container_name=${{ env.CONTAINERNAME }}"
- name: Terraform Plan
run: |
terraform plan -out my.plan \
-var="spring_cloud_service=${{ needs.deploy_lz_standard.outputs.spring_apps_service_name }}" \
-var="subscription_id=${{ env.SUBSCRIPTION_ID}}" \
-var="mysql_server_admin_password=${{ env.MYSQL_ADMIN_PASSWORD }}" \
-var="mysql_server_admin_username=${{ env.MYSQL_ADMIN_USERNAME }}"
- name: Terraform Apply
run: terraform apply my.plan
build:
name: Build and Deploy Pet Clinic Microservices
needs: [deploy_pet_clinic_infra, deploy_lz_standard, conditions]
runs-on: ubuntu-latest
env:
SPRING_APPS_SERVICE_NAME: ${{ needs.deploy_lz_standard.outputs.spring_apps_service_name }}
steps:
- name: Checkout pet clinic sample
uses: actions/checkout@v3
with:
repository: Azure-Samples/spring-petclinic-microservices.git
path: pet-clinic
ref: azure
- name: Set up JDK 17
uses: actions/setup-java@v2
with:
java-version: 17
distribution: "microsoft"
cache: maven
- name: maven build, clean
working-directory: pet-clinic
run: |
mvn clean package -DskipTests
- name: Azure CLI Login
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Deploy api-gateway
uses: azure/spring-apps-deploy@v1
with:
azure-subscription: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
action: deploy
service-name: ${{ needs.deploy_lz_standard.outputs.spring_apps_service_name }}
app-name: ${{ env.API_GATEWAY }}
use-staging-deployment: false
package: ${{ github.workspace }}/pet-clinic/${{ env.API_GATEWAY_JAR }}
jvm-options: -Xms2048m -Xmx2048m
runtime-version: Java_17
environment-variables: -SPRING_PROFILES_ACTIVE passwordless
- name: Deploy admin-server
uses: azure/spring-apps-deploy@v1
with:
azure-subscription: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
action: deploy
service-name: ${{ env.SPRING_APPS_SERVICE_NAME }}
app-name: ${{ env.ADMIN_SERVER }}
use-staging-deployment: false
package: ${{ github.workspace }}/pet-clinic/${{ env.ADMIN_SERVER_JAR }}
jvm-options: -Xms2048m -Xmx2048m
runtime-version: Java_17
environment-variables: "-SPRING_PROFILES_ACTIVE passwordless"
- name: Deploy customers-service
uses: azure/spring-apps-deploy@v1
with:
azure-subscription: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
action: deploy
service-name: ${{ env.SPRING_APPS_SERVICE_NAME }}
app-name: ${{ env.CUSTOMERS_SERVICE }}
use-staging-deployment: false
package: ${{ github.workspace }}/pet-clinic/${{ env.CUSTOMERS_SERVICE_JAR }}
jvm-options: -Xms2048m -Xmx2048m
runtime-version: Java_17
environment-variables: -SPRING_PROFILES_ACTIVE passwordless
- name: Deploy vets-service
uses: azure/spring-apps-deploy@v1
with:
azure-subscription: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
action: deploy
service-name: ${{ env.SPRING_APPS_SERVICE_NAME }}
app-name: ${{ env.VETS_SERVICE }}
use-staging-deployment: false
package: ${{ github.workspace }}/pet-clinic/${{ env.VETS_SERVICE_JAR }}
jvm-options: -Xms2048m -Xmx2048m
runtime-version: Java_17
environment-variables: -SPRING_PROFILES_ACTIVE passwordless
- name: Deploy visits-service
uses: azure/spring-apps-deploy@v1
with:
azure-subscription: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
action: deploy
service-name: ${{ env.SPRING_APPS_SERVICE_NAME }}
app-name: ${{ env.VISITS_SERVICE }}
use-staging-deployment: false
package: ${{ github.workspace }}/pet-clinic/${{ env.VISITS_SERVICE_JAR }}
jvm-options: -Xms2048m -Xmx2048m
runtime-version: Java_17
environment-variables: -SPRING_PROFILES_ACTIVE passwordless
destroy_pet_clinic_infra:
name: Destroy Pet Clinic Infrastructure
needs:
[
conditions,
deploy_lz_standard,
deploy_lz_shared,
deploy_pet_clinic_infra,
build,
]
runs-on: ubuntu-latest
if: needs.conditions.outputs.destroy == 'true'
defaults:
run:
working-directory: Scenarios/sample-apps/petclinic/terraform
steps:
- name: Checkout this repository
uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2.0.3
with:
terraform_wrapper: false
- name: Terraform Init
run: |
terraform init \
-backend-config="resource_group_name=${{ env.TFSTATE_RG }}" \
-backend-config="storage_account_name=${{ env.STORAGEACCOUNTNAME }}" \
-backend-config="container_name=${{ env.CONTAINERNAME }}"
- name: Terraform Plan
run: |
terraform plan -destroy -out my.plan \
-var="spring_cloud_service=${{ needs.deploy_lz_standard.outputs.spring_apps_service_name }}" \
-var="subscription_id=${{ env.SUBSCRIPTION_ID}}" \
-var="mysql_server_admin_password=${{ env.MYSQL_ADMIN_PASSWORD }}" \
-var="mysql_server_admin_username=${{ env.MYSQL_ADMIN_USERNAME }}"
- name: Terraform Apply
run: terraform apply my.plan
destroy_lz_standard:
needs: [conditions, deploy_lz_standard, destroy_pet_clinic_infra]
name: Destroy 06 LZ Spring Apps Standard
runs-on: ubuntu-latest
if: needs.conditions.outputs.destroy == 'true'
defaults:
run:
working-directory: Scenarios/ASA-Secure-Baseline/Terraform/06-LZ-SpringApps-Standard
steps:
- name: Checkout this repository
uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2.0.3
with:
terraform_wrapper: false
- name: Terraform Init
run: |
terraform init \
-backend-config="resource_group_name=${{ env.TFSTATE_RG }}" \
-backend-config="storage_account_name=${{ env.STORAGEACCOUNTNAME }}" \
-backend-config="container_name=${{ env.CONTAINERNAME }}"
- name: Terraform Plan
run: |
terraform plan -destroy -out my.plan \
-var="state_sa_rg=${{ env.TFSTATE_RG }}" \
-var="state_sa_name=${{ env.STORAGEACCOUNTNAME }}" \
-var="state_sa_container_name=${{ env.CONTAINERNAME }}" \
-var="location=${{ env.REGION }}" \
-var="name_prefix=${{ env.NAME_PREFIX }}" \
-var="environment=${{ env.ENVIRONMENT }}" \
-var="SRINGAPPS_SPN_OBJECT_ID=${{ env.SRINGAPPS_SPN_OBJECT_ID }}"
- name: Terraform Apply
run: terraform apply my.plan
destroy_hub_firewall:
needs: [destroy_lz_standard, conditions]
name: Destroy 05 Hub Firewall
runs-on: ubuntu-latest
if: needs.conditions.outputs.destroy == 'true'
defaults:
run:
working-directory: Scenarios/ASA-Secure-Baseline/Terraform/05-Hub-AzureFirewall
steps:
- name: Checkout this repository
uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2.0.3
with:
terraform_wrapper: false
- name: Terraform Init
run: |
terraform init \
-backend-config="resource_group_name=${{ env.TFSTATE_RG }}" \
-backend-config="storage_account_name=${{ env.STORAGEACCOUNTNAME }}" \
-backend-config="container_name=${{ env.CONTAINERNAME }}"
- name: Terraform Plan
run: |
terraform plan -destroy -out my.plan \
-var="state_sa_rg=${{ env.TFSTATE_RG }}" \
-var="state_sa_name=${{ env.STORAGEACCOUNTNAME }}" \
-var="state_sa_container_name=${{ env.CONTAINERNAME }}" \
-var="location=${{ env.REGION }}" \
-var="name_prefix=${{ env.NAME_PREFIX }}" \
-var="environment=${{ env.ENVIRONMENT }}" \
-var="SRINGAPPS_SPN_OBJECT_ID=${{ env.SRINGAPPS_SPN_OBJECT_ID }}"
- name: Terraform Apply
run: terraform apply my.plan
destroy_lz_shared:
needs: [destroy_hub_firewall, conditions]
name: Destroy 04 LZ Shared Resources
runs-on: ubuntu-latest
if: needs.conditions.outputs.destroy == 'true'
defaults:
run:
working-directory: Scenarios/ASA-Secure-Baseline/Terraform/04-LZ-SharedResources
steps:
- name: Checkout this repository
uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2.0.3
with:
terraform_wrapper: false
- name: Terraform Init
run: |
terraform init \
-backend-config="resource_group_name=${{ env.TFSTATE_RG }}" \
-backend-config="storage_account_name=${{ env.STORAGEACCOUNTNAME }}" \
-backend-config="container_name=${{ env.CONTAINERNAME }}"
- name: Terraform Plan
run: |
terraform plan -destroy -out my.plan \
-var="state_sa_rg=${{ env.TFSTATE_RG }}" \
-var="state_sa_name=${{ env.STORAGEACCOUNTNAME }}" \
-var="state_sa_container_name=${{ env.CONTAINERNAME }}" \
-var="location=${{ env.REGION }}" \
-var="name_prefix=${{ env.NAME_PREFIX }}" \
-var="environment=${{ env.ENVIRONMENT }}" \
-var="SRINGAPPS_SPN_OBJECT_ID=${{ env.SRINGAPPS_SPN_OBJECT_ID }}" \
-var="jump_host_admin_username=${{ env.JUMP_BOX_USERNAME }}" \
-var="jump_host_password=${{ env.JUMP_BOX_PASSWORD}}"
- name: Terraform Apply
run: terraform apply my.plan
destroy_lz_network:
needs: [destroy_lz_shared, conditions]
name: Destroy 03 LZ Network
runs-on: ubuntu-latest
if: needs.conditions.outputs.destroy == 'true'
defaults:
run:
working-directory: Scenarios/ASA-Secure-Baseline/Terraform/03-LZ-Network
steps:
- name: Checkout this repository
uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2.0.3
with:
terraform_wrapper: false
- name: Terraform Init
run: |
terraform init \
-backend-config="resource_group_name=${{ env.TFSTATE_RG }}" \
-backend-config="storage_account_name=${{ env.STORAGEACCOUNTNAME }}" \
-backend-config="container_name=${{ env.CONTAINERNAME }}"
- name: Terraform Plan
run: |
terraform plan -destroy -out my.plan \
-var="state_sa_rg=${{ env.TFSTATE_RG }}" \
-var="state_sa_name=${{ env.STORAGEACCOUNTNAME }}" \
-var="state_sa_container_name=${{ env.CONTAINERNAME }}" \
-var="location=${{ env.REGION }}" \
-var="name_prefix=${{ env.NAME_PREFIX }}" \
-var="environment=${{ env.ENVIRONMENT }}" \
-var="SRINGAPPS_SPN_OBJECT_ID=${{ env.SRINGAPPS_SPN_OBJECT_ID }}"
- name: Terraform Apply
run: terraform apply my.plan
destroy_hub_network:
name: Destroy 02 Hub Network
needs: [destroy_lz_network, conditions]
runs-on: ubuntu-latest
if: needs.conditions.outputs.destroy == 'true'
defaults:
run:
working-directory: Scenarios/ASA-Secure-Baseline/Terraform/02-Hub-Network
steps:
- name: Checkout this repository
uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2.0.3
with:
terraform_wrapper: false
- name: Terraform Init
run: |
terraform init \
-backend-config="resource_group_name=${{ env.TFSTATE_RG }}" \
-backend-config="storage_account_name=${{ env.STORAGEACCOUNTNAME }}" \
-backend-config="container_name=${{ env.CONTAINERNAME }}"
- name: Terraform Plan
run: |
terraform plan -destroy -out my.plan \
-var="state_sa_rg=${{ env.TFSTATE_RG }}" \
-var="state_sa_name=${{ env.STORAGEACCOUNTNAME }}" \
-var="state_sa_container_name=${{ env.CONTAINERNAME }}" \
-var="location=${{ env.REGION }}" \
-var="name_prefix=${{ env.NAME_PREFIX }}" \
-var="environment=${{ env.ENVIRONMENT }}" \
-var="SRINGAPPS_SPN_OBJECT_ID=${{ env.SRINGAPPS_SPN_OBJECT_ID }}"
- name: Terraform Apply
run: terraform apply my.plan

Просмотреть файл

@ -1,101 +0,0 @@
# Azure Spring Apps Landing Zone Accelerator
Azure Landing Zone Accelerators are architectural guidance, reference architecture, reference implementations and automation packaged to deploy workload platforms on Azure at Scale and aligned with industry proven practices.
Azure Spring apps Landing Zone Accelerator represents the strategic design path and target technical state for an Azure Spring Apps Service deployment.
This repository provides packaged guidance for customer scenarios, reference architecture, reference implementation, tooling, design area guidance, sample spring apps deployed after provisioning the infrastructure using the accelerator. The architectural approach can be used as design guidance for greenfield implementation and as an assessment for brownfield customers already using Spring boot apps.
## Enterprise-Scale Architecture
The enterprise architecture is broken down into key design areas, where you can find the links to each at:
| Design Area|Considerations and Recommendations|
|:--------------:|:--------------:|
| Identity and Access Management|[Design Considerations and Recommendations](/docs/Design-Areas/LZA-ASA-IAM.md)
| Network Topology and Connectivity|[Design Considerations and Recommendations](/docs/Design-Areas/LZA-ASA-Network-Topology.md)
| Management and Monitoring|[Design Considerations and Recommendations](/docs/Design-Areas/LZA-ASA-Mangement%20And%20Operations.md)
| Security, Governance, and Compliance|[Design Considerations and Recommendations](/docs/Design-Areas/LZA-ASA-Security.md)
## Enterprise-Scale Reference Implementation
This repository contains instructions for creating an
[Azure Spring Apps](https://docs.microsoft.com/azure/spring-cloud/spring-cloud-overview)
reference architecture that can be used for deploying Spring Boot
applications in a typical enterprise landing zone design.
It uses a [hub and spoke architecture](https://docs.microsoft.com/azure/architecture/reference-architectures/hybrid-networking/hub-spoke)
with a single spoke. East/West traffic (traffic between resources in the hub and resources in the
spoke) is filtered with Network Security Groups and North/South traffic (traffic between the
Internet and resources in the hub or spoke) is routed through and mediated with an instance of
Azure Firewall.
![lab image](images/architecture-private.svg)
* Azure Spring Apps is deployed using [vnet-injection](https://docs.microsoft.com/azure/spring-cloud/spring-cloud-tutorial-deploy-in-azure-virtual-network)
to allow for mediation inbound and outbound traffic to the Azure Spring Apps Instance and deployed applications.
* The Azure Firewall instance has been configured to write its logs to a Log Analytics Workspace.
You can leverage [these Kusto queries](https://docs.microsoft.com/azure/firewall/log-analytics-samples)
to analyze Azure Firewall log data written to Log Analytics.
* Hub and Spoke Virtual Networks are configured to use Azure Firewall for DNS queries
utilizing the [DNS Proxy feature](https://docs.microsoft.com/azure/firewall/dns-settings#dns-proxy)
of Azure Firewall.
* Azure Private DNS zones for Azure Spring Apps and support services deployed with Private Endpoints
* A single Windows Server 2022 Virtual Machine the hub Virtual Network for testing access to
applications deployed into the Azure Spring Apps instance. This virtual machine is configured
with the Microsoft Monitoring Agent and is integrated with the Log Analytics Workspace. This VM is
not exposed to the internet and is only accessible via Azure Bastion (for brevity, both the VM and Azure
Bastion are not shown in the diagram).
* Log Analytics Workspace where Azure Spring Apps, Azure Firewall, and the virtual machine deliver
logs and metrics.
* Instance of Azure Key Vault deployed with a Private Endpoint for secrets and certificates storage
for applications deployed to Azure Spring Apps
* Instance of Azure Bastion for connection to the Windows Server 2022 virtual machine running in the hub virtual network.
For Azure Spring Apps Standard SKU:
* Instance of Azure Database for MySQL flexible server deployed with VNET Integration. This can be used to deploy the PetClinic sample app described in this document.
For Azure Spring Apps Enterprise SKU:
* Instance of Azure Database for PostgreSQL flexible server deployed with VNET Integration and Azure Cache for Redis with Private endpoint.
Deployment Details:
| Deployment Methodology | GitHub Actions
|--------------|--------------|
|[Terraform]|Coming soon|
|[Bicep]|Coming soon|
Cost estimation:
---
## Got a feedback
Please leverage issues if you have any feedback or request on how we can improve on this repository.
## Data Collection
The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the repository. There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft's privacy statement. Our privacy statement is located at https://go.microsoft.com/fwlink/?LinkId=521839. You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices.
## Telemetry Configuration
Telemetry collection is on by default.
To opt-out, set the variable enableTelemetry to false in Bicep/ARM file and disable_terraform_partner_id to false on Terraform files.
## Contributing
This project welcomes contributions and suggestions. Most contributions require you to agree to a
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
When you submit a pull request, a CLA bot will automatically determine whether you need to provide
a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions
provided by the bot. You will only need to do this once across all repos using our CLA.
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
## Trademarks
This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft
trademarks or logos is subject to and must follow
[Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general).
Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship.
Any use of third-party trademarks or logos are subject to those third-party's policies.

Просмотреть файл

@ -0,0 +1,3 @@
output "spring_apps_service_name" {
value = azurerm_spring_cloud_service.sc_standard.name
}

Просмотреть файл

@ -0,0 +1,134 @@
# Deploying the Landing Zones and Pet-Clinic microservices E2E using GitHub Actions
To deploy the Azure Spring App Landing Zoner and deploy the petclinic microservices to Azure Spring Apps, we'll setup a GitHub Actions CI/CD workflow that will build and deploy our application whenever we push new commits to the main branch of our repository.
## What's CI/CD?
CI/CD stands for _Continuous Integration_ and _Continuous Delivery_.
Continuous Integration is a software development practice that requires developers to integrate code into a shared repository several times a day.
Each integration can then be verified by an automated build and automated tests.
By doing so, you can detect errors quickly, and locate them more easily.
Continuous Delivery pushes this practice further, by preparing for a release to production after each successful build.
By doing so, you can get working software into the hands of users faster.
## What's GitHub Actions?
[GitHub Actions](https://github.com/features/actions) is a service that lets you automate your software development workflows.
It allows you to run workflows that can be triggered by any event on the GitHub platform, such as opening a pull request or pushing a commit to a repository.
It's a great way to automate your CI/CD pipelines, and it's free for public repositories.
## Setting Up GitHub Actions for deployment
To set up GitHub Actions for deployment, we'll need to use the new workflow file in our repository.
This file will contain the instructions for our CI/CD pipeline.
## Setup secrets
We are using different secrets in our workflow: JUMP_BOX_PASSWORD and MYSQL_ADMIN_PASSWORD. Secrets in GitHub are encrypted and allow you to store sensitive information such as passwords or API keys, and use them in your workflows using the ${{ secrets.MY_SECRET }} syntax.
In GitHub, secrets can be defined at three different levels:
* Repository level: secrets defined at the repository level are available in all workflows of the repository.
* Organization level: secrets defined at the organization level are available in all workflows of the GitHub organization.
* Environment level: secrets defined at the environment level are available only in workflows referencing the specified environment.
For this workshop, well define our secrets at the repository level. To do so, go to the Settings tab of your repository, and select Secrets then Actions under it, in the left menu.
Then select New repository secret and create secrets for JUMP_BOX_PASSWORD and MYSQL_ADMIN_PASSWORD.
## [!TIP]
You can also use the <https://cli.github.com[GitHub> CLI] to define your secrets, using the command `gh secret set <MY_SECRET> -b"<SECRET_VALUE>" -R <repository_url>`
## Creating an Azure Service Principal
In order to deploy our Landing Zone and application to Azure Spring Apps, we'll need to create an Azure Service Principal.
This is an identity that can be used to authenticate to Azure, and that can be granted access to specific resources.
To create a new Service Principal, run the following commands:
```bash
SUBSCRIPTION_ID=$(
az account show \
--query id \
--output tsv \
--only-show-errors
)
AZURE_CREDENTIALS=$(
MSYS_NO_PATHCONV=1 az ad sp create-for-rbac \
--name="sp-${PROJECT}-${UNIQUE_IDENTIFIER}" \
--role="Contributor" \
--scopes="/subscriptions/$SUBSCRIPTION_ID" \
--sdk-auth \
--only-show-errors
)
echo $AZURE_CREDENTIALS
echo $SUBSCRIPTION_ID
```
Then just like in the previous step, create a new secret in your repository named `AZURE_SUBSCRIPTION_ID`, `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, `AZURE_TENANT_ID`. You can copy paste these values from the AZURE_CREDENTIALS value returned in the cli. Also create another secret for `AZURE_CREDENTIALS` and paste the value of the `AZURE_CREDENTIALS` variable as the secret value (make sure to _copy the entire JSon_).
![GitHub Secrets](../../../images/github_asa_secrets.png)
## Modify variables in `deploy.yml`
The workflow file can be found in your repository with the path [`.github/workflows/deploy.yml`](../../../.github/workflows/deploy.yml) :
* Replace the value of the `SRINGAPPS_SPN_OBJECT_ID` environment variable in the [deploy.yaml](../../../.github/workflows/deploy.yml) file with the value of the the Object ID for the "Azure Spring Apps Resource Provider" service principal in your Azure AD Tenant.
You use the command below to obtain the value of the variable:
az ad sp show --id e8de9221-a19c-4c81-b814-fd37c6caf9d2 --query id --output tsv
* Replace the value of TFSTATE_RG, STORAGEACCOUNTNAME and CONTAINERNAME in [deploy.yaml](../../../.github/workflows/deploy.yml) to point to your Terraform backend.
* You can also set the deploy_firewall and destroy values in [deploy.yaml](../../../.github/workflows/deploy.yml) depending on your usecase.
This workflow will be triggered every time a commit is pushed to the `main` branch.
It will then run a job with the following steps:
* Deploy 02 Hub Network
* Deploy 03 LZ Network
* Deploy 04 LZ Shared Resources
* Deploy 05 Hub Firewall
* Deploy 06 LZ Spring Apps Standard
* Deploy Pet Clinic Infrastructure
After the above steps are successful, you will have a functioning landing zone and the Azure Spring App instance available. After the above, the workflow also runs the below build step to build and deploy the petclinic microservices in the Azure Spring Apps instance.
* Build and Deploy Pet Clinic Microservices
Make sure to keep the correct indentation for the steps if you make changes to the deploy.yaml file directly.
YAML is very sensitive to indentation.
## [!TIP]
* If you do not want to provision the firewall or destroy the E2E infra once the pipeline run in complete, make sure to set those values to false in the deploy.yaml
* If a particular step errors out you can run only that step from the pipeline directly.Most errors should be transient errors.
## Running the workflow
Now that we've defined our workflow and prepared everything, we can run it to deploy our landing zone and the petclinic application to Azure Spring Apps.
Commit and push your changes to your repository, and go to the `Actions` tab of your repository to see the workflow running.
It should take a few minutes to complete.
A successful run using github actions should look like below:
![successful e2e run](../../../images/github_asa_successful_run.png)
## Testing the deployed application
Once your workflow is completed, let's make a quick test on our deployed apps.
First we need to get the ingress URL by running the following command:
```bash
az spring app show -g rg-springlza-APPS -s spring-springlza-dev-o7o6 \
--name api-gateway --query "properties.url" --output tsv
```
Then we can use `curl` to test our applications using the above endpoint. This assumes that there's no Application Gateway and you would access your spring app using the spring apps ingress url for the api-gateway app instance. Since the applications are deployed in an internal only environment you would need to do the curl from a jumpbox or bastion host.

Просмотреть файл

@ -95,6 +95,8 @@ In this example, there is a common variable defintions file [parameters.tfvars](
8. [Cleanup](./08-cleanup.md)
9. [E2E Deployment using GitHub Action](./09-e2e-githubactions.md)
## Known Issues / Notes
- When destroying Azure Spring Apps **Enterprise**, there is an issue with the API Portal destruction where destruction will fail with error "Please unassign public endpoint before deleting API Portal.". This issues does not apply to Spring Apps Standard Edition.
- A bug has been filed with the AZURERM terraform provider Github

Просмотреть файл

@ -0,0 +1,4 @@
## Pet clinic sample app deployment
For instruction to deploy the petclinic sample app please see instructions here : [E2E Deployment using GitHub Action](../../ASA-Secure-Baseline/Terraform/09-e2e-githubaction.md)

Просмотреть файл

@ -0,0 +1,45 @@
module "api_gateway" {
source = "./modules/app_instance"
instance_name = var.api_gateway
resource_group_name = data.azurerm_spring_cloud_service.spring_cloud.resource_group_name
spring_cloud_service_name = data.azurerm_spring_cloud_service.spring_cloud.name
service_connection_id = azurerm_mysql_flexible_database.petclinic_database.id
is_public = true
}
module "admin_server" {
source = "./modules/app_instance"
instance_name = var.admin_server
resource_group_name = data.azurerm_spring_cloud_service.spring_cloud.resource_group_name
spring_cloud_service_name = data.azurerm_spring_cloud_service.spring_cloud.name
service_connection_id = azurerm_mysql_flexible_database.petclinic_database.id
is_public = true
}
module "customers_service" {
source = "./modules/app_instance"
instance_name = var.customers_service
resource_group_name = data.azurerm_spring_cloud_service.spring_cloud.resource_group_name
spring_cloud_service_name = data.azurerm_spring_cloud_service.spring_cloud.name
service_connection_id = azurerm_mysql_flexible_database.petclinic_database.id
}
module "vets_service" {
source = "./modules/app_instance"
instance_name = var.vets_service
resource_group_name = data.azurerm_spring_cloud_service.spring_cloud.resource_group_name
spring_cloud_service_name = data.azurerm_spring_cloud_service.spring_cloud.name
service_connection_id = azurerm_mysql_flexible_database.petclinic_database.id
}
module "visits_service" {
source = "./modules/app_instance"
instance_name = var.visits_service
resource_group_name = data.azurerm_spring_cloud_service.spring_cloud.resource_group_name
spring_cloud_service_name = data.azurerm_spring_cloud_service.spring_cloud.name
service_connection_id = azurerm_mysql_flexible_database.petclinic_database.id
}

Просмотреть файл

@ -0,0 +1,51 @@
# Azure provider version
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "= 3.32.0"
}
azuread = {
source = "hashicorp/azuread"
version = "2.31.0"
}
}
backend "azurerm" {
# resource_group_name = "" # Partial configuration, provided during "terraform init"
# storage_account_name = "" # Partial configuration, provided during "terraform init"
# container_name = "" # Partial configuration, provided during "terraform init"
key = "lz-petclinic"
}
}
provider "azurerm" {
subscription_id = var.subscription_id
features {}
}
locals {
vnet_spoke_name = "vnet-springlza-${data.azurerm_resource_group.spoke_rg.location}-SPOKE"
}
data "azurerm_resource_group" "spoke_rg" {
name = var.resource_group
}
data "azurerm_resource_group" "private_zones_rg" {
name = var.private_zones_resource_group_name
}
data "azurerm_resource_group" "spring_apps_rg" {
name = var.spring_cloud_resource_group_name
}
data "azurerm_spring_cloud_service" "spring_cloud" {
name = var.spring_cloud_service
resource_group_name = var.spring_cloud_resource_group_name
}
data "azurerm_virtual_network" "spoke" {
name = local.vnet_spoke_name
resource_group_name = data.azurerm_resource_group.spoke_rg.name
}

Просмотреть файл

@ -0,0 +1,70 @@
variable "instance_name" {
type = string
}
variable "resource_group_name" {
type = string
}
variable "spring_cloud_service_name" {
type = string
}
variable "is_public" {
type = bool
default = false
}
variable "service_connection_id" {
type = string
}
locals {
service_connection_name = "${replace(var.instance_name, "-", "_")}_service_connection"
}
resource "azurerm_spring_cloud_app" "app_instance" {
name = var.instance_name
resource_group_name = var.resource_group_name
service_name = var.spring_cloud_service_name
is_public = var.is_public
identity {
type = "SystemAssigned"
}
}
resource "azurerm_spring_cloud_java_deployment" "app_instance" {
name = "default"
spring_cloud_app_id = azurerm_spring_cloud_app.app_instance.id
instance_count = 1
jvm_options = "-Xms2048m -Xmx2048m"
runtime_version = "Java_17"
quota {
cpu = "2"
memory = "2Gi"
}
lifecycle {
ignore_changes = [
environment_variables
]
}
}
resource "azurerm_spring_cloud_active_deployment" "app_instance" {
spring_cloud_app_id = azurerm_spring_cloud_app.app_instance.id
deployment_name = azurerm_spring_cloud_java_deployment.app_instance.name
}
resource "azurerm_spring_cloud_connection" "app_instance" {
name = local.service_connection_name
spring_cloud_id = azurerm_spring_cloud_java_deployment.app_instance.id
target_resource_id = var.service_connection_id
authentication {
type = "systemAssignedIdentity"
}
}

Просмотреть файл

@ -0,0 +1,98 @@
resource "random_string" "random" {
length = 4
upper = false
special = false
}
locals {
mysql_server_name = "pcsms-db-${lower(var.resource_group)}-${random_string.random.result}"
}
resource "azurerm_subnet" "mysql" {
name = "snet-mysql"
resource_group_name = data.azurerm_resource_group.spoke_rg.name
virtual_network_name = data.azurerm_virtual_network.spoke.name
address_prefixes = ["${var.mysql_CIDR}"]
service_endpoints = ["Microsoft.Storage"]
delegation {
name = "fs"
service_delegation {
name = "Microsoft.DBforMySQL/flexibleServers"
actions = [
"Microsoft.Network/virtualNetworks/subnets/join/action",
]
}
}
}
resource "azurerm_network_security_group" "mysql" {
name = "snet-mysql-nsg"
location = data.azurerm_resource_group.spoke_rg.location
resource_group_name = data.azurerm_resource_group.spoke_rg.name
}
resource "azurerm_private_dns_zone" "mysql" {
name = "private.mysql.database.azure.com"
resource_group_name = data.azurerm_resource_group.private_zones_rg.name
}
resource "azurerm_private_dns_zone_virtual_network_link" "mysql" {
name = "mysql-spoke-link"
resource_group_name = data.azurerm_resource_group.private_zones_rg.name
private_dns_zone_name = azurerm_private_dns_zone.mysql.name
virtual_network_id = data.azurerm_virtual_network.spoke.id
}
resource "azurerm_mysql_flexible_server" "mysql" {
name = local.mysql_server_name
resource_group_name = data.azurerm_resource_group.spring_apps_rg.name
location = data.azurerm_resource_group.spring_apps_rg.location
administrator_login = var.mysql_server_admin_username
administrator_password = var.mysql_server_admin_password
backup_retention_days = 7
delegated_subnet_id = azurerm_subnet.mysql.id
private_dns_zone_id = azurerm_private_dns_zone.mysql.id
sku_name = "GP_Standard_D2ds_v4"
lifecycle {
ignore_changes = [
zone
]
}
depends_on = [azurerm_private_dns_zone_virtual_network_link.mysql]
}
resource "azurerm_mysql_flexible_database" "petclinic_database" {
name = var.mysql_database_name
resource_group_name = data.azurerm_resource_group.spring_apps_rg.name
server_name = azurerm_mysql_flexible_server.mysql.name
charset = "utf8"
collation = "utf8_unicode_ci"
}
resource "azurerm_mysql_flexible_server_firewall_rule" "allazureips" {
name = "allAzureIPs"
resource_group_name = data.azurerm_resource_group.spring_apps_rg.name
server_name = azurerm_mysql_flexible_server.mysql.name
start_ip_address = "0.0.0.0"
end_ip_address = "0.0.0.0"
}
resource "azurerm_mysql_flexible_server_configuration" "timeout" {
name = "interactive_timeout"
resource_group_name = data.azurerm_resource_group.spring_apps_rg.name
server_name = azurerm_mysql_flexible_server.mysql.name
value = "2147483"
}
resource "azurerm_mysql_flexible_server_configuration" "time_zone" {
name = "time_zone"
resource_group_name = data.azurerm_resource_group.spring_apps_rg.name
server_name = azurerm_mysql_flexible_server.mysql.name
value = "-8:00" // Add appropriate offset based on your region.
depends_on = [
azurerm_mysql_flexible_server.mysql
]
}

Просмотреть файл

@ -0,0 +1,11 @@
output "mysql_database_name" {
value = azurerm_mysql_flexible_database.petclinic_database.name
}
output "mysql_server_name" {
value = azurerm_mysql_flexible_server.mysql.fqdn
}

Просмотреть файл

@ -0,0 +1,11 @@
subscription_id = "<Your subscription containing spring apps instance>"
# Name of Spring Cloud Service, this is generated when deploying the ASA-Secure-Baseline
spring_cloud_service = "<Your spring apps instance name>"
# Supply a password for the mysql server administrator
####################################################
# Do not commit passwords to your repository this should be done for local develop, deployment purposes only
####################################################
mysql_server_admin_password = "<Password for MySQL Administrator>"

Просмотреть файл

@ -0,0 +1,65 @@
variable "subscription_id" {
type = string
default = ""
}
variable "resource_group" {
type = string
default = "rg-springlza-SPOKE"
}
variable "spring_cloud_service" {
type = string
}
variable "spring_cloud_resource_group_name" {
type = string
default = "rg-springlza-APPS"
}
variable "private_zones_resource_group_name" {
type = string
default = "rg-springlza-PRIVATEZONES"
}
variable "key_vault_rg" {
type = string
default = "rg-springlza-SHARED"
}
variable "api_gateway" {
type = string
default = "api-gateway"
}
variable "admin_server" {
type = string
default = "admin-server"
}
variable "customers_service" {
type = string
default = "customers-service"
}
variable "visits_service" {
type = string
default = "visits-service"
}
variable "vets_service" {
type = string
default = "vets-service"
}
variable "mysql_server_admin_username" {
type = string
default = "sqlAdmin"
}
variable "mysql_server_admin_password" {
type = string
}
variable "mysql_database_name" {
type = string
default = "petclinic"
}
variable "mysql_CIDR" {
type = string
default = "10.1.6.0/24"
}

Просмотреть файл

Просмотреть файл

Просмотреть файл

До

Ширина:  |  Высота:  |  Размер: 59 KiB

После

Ширина:  |  Высота:  |  Размер: 59 KiB

Просмотреть файл

До

Ширина:  |  Высота:  |  Размер: 66 KiB

После

Ширина:  |  Высота:  |  Размер: 66 KiB

Просмотреть файл

До

Ширина:  |  Высота:  |  Размер: 34 KiB

После

Ширина:  |  Высота:  |  Размер: 34 KiB

Просмотреть файл

До

Ширина:  |  Высота:  |  Размер: 141 KiB

После

Ширина:  |  Высота:  |  Размер: 141 KiB

Просмотреть файл

Просмотреть файл

Просмотреть файл

До

Ширина:  |  Высота:  |  Размер: 59 KiB

После

Ширина:  |  Высота:  |  Размер: 59 KiB

Просмотреть файл

До

Ширина:  |  Высота:  |  Размер: 66 KiB

После

Ширина:  |  Высота:  |  Размер: 66 KiB

Просмотреть файл

До

Ширина:  |  Высота:  |  Размер: 34 KiB

После

Ширина:  |  Высота:  |  Размер: 34 KiB

Просмотреть файл

До

Ширина:  |  Высота:  |  Размер: 141 KiB

После

Ширина:  |  Высота:  |  Размер: 141 KiB

Просмотреть файл

Просмотреть файл

Просмотреть файл

Просмотреть файл

Просмотреть файл

Просмотреть файл

До

Ширина:  |  Высота:  |  Размер: 59 KiB

После

Ширина:  |  Высота:  |  Размер: 59 KiB

Просмотреть файл

До

Ширина:  |  Высота:  |  Размер: 66 KiB

После

Ширина:  |  Высота:  |  Размер: 66 KiB

Просмотреть файл

До

Ширина:  |  Высота:  |  Размер: 141 KiB

После

Ширина:  |  Высота:  |  Размер: 141 KiB

Просмотреть файл

Просмотреть файл

До

Ширина:  |  Высота:  |  Размер: 92 KiB

После

Ширина:  |  Высота:  |  Размер: 92 KiB

Просмотреть файл

До

Ширина:  |  Высота:  |  Размер: 442 KiB

После

Ширина:  |  Высота:  |  Размер: 442 KiB

Просмотреть файл

До

Ширина:  |  Высота:  |  Размер: 161 KiB

После

Ширина:  |  Высота:  |  Размер: 161 KiB

Просмотреть файл

До

Ширина:  |  Высота:  |  Размер: 141 KiB

После

Ширина:  |  Высота:  |  Размер: 141 KiB

Просмотреть файл

До

Ширина:  |  Высота:  |  Размер: 638 KiB

После

Ширина:  |  Высота:  |  Размер: 638 KiB

Просмотреть файл

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше