{"id":24563706,"url":"https://github.com/mildronize/actions-az-webapp-swap","last_synced_at":"2025-04-19T19:05:12.062Z","repository":{"id":37031212,"uuid":"489317076","full_name":"mildronize/actions-az-webapp-swap","owner":"mildronize","description":"GitHub Actions for Safely Swap Azure App Service Slot","archived":false,"fork":false,"pushed_at":"2023-02-28T22:07:31.000Z","size":4158,"stargazers_count":6,"open_issues_count":16,"forks_count":1,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-03-29T12:03:56.016Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mildronize.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":null,"support":null}},"created_at":"2022-05-06T10:46:57.000Z","updated_at":"2023-01-31T19:48:52.000Z","dependencies_parsed_at":"2023-02-10T12:16:53.851Z","dependency_job_id":null,"html_url":"https://github.com/mildronize/actions-az-webapp-swap","commit_stats":{"total_commits":65,"total_committers":2,"mean_commits":32.5,"dds":0.01538461538461533,"last_synced_commit":"b07923f093de4e17c7c15c0e16e69a40d2c2802c"},"previous_names":[],"tags_count":11,"template":false,"template_full_name":"actions/typescript-action","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mildronize%2Factions-az-webapp-swap","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mildronize%2Factions-az-webapp-swap/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mildronize%2Factions-az-webapp-swap/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mildronize%2Factions-az-webapp-swap/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mildronize","download_url":"https://codeload.github.com/mildronize/actions-az-webapp-swap/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249771674,"owners_count":21323145,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-01-23T10:15:20.279Z","updated_at":"2025-04-19T19:05:12.023Z","avatar_url":"https://github.com/mildronize.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Safely Swap Azure App Service Slot v2\n\n[![build-test](https://github.com/mildronize/actions-az-webapp-swap/actions/workflows/test.yml/badge.svg)](https://github.com/mildronize/actions-az-webapp-swap/actions/workflows/test.yml) \n[![Coverage Status](https://coveralls.io/repos/github/mildronize/actions-az-webapp-swap/badge.svg?branch=main)](https://coveralls.io/github/mildronize/actions-az-webapp-swap?branch=main)\n\nHave you ever swapped Azure App Service Slots for more than 30 sites?\n\nSwapping multiple Azure App Service Slots can cause some unwanted swapping of App Settings which might cause an incident in the production.\n\nTo prevent swapping the App Setting, we need to set `SlotSetting` parameters in the key/value of the App Settings, because default `SlotSetting` is false.\n\nTo be more clear, \n- watch YouTube demo: https://www.youtube.com/watch?v=q5o6twX9gEg\n\n- [Checkout Slide](https://docs.google.com/presentation/d/1vDox6HKISkUPyY_Jok39Dv9SHjrWa3CuJ9xLdaMXsUk/edit#slide=id.gc6f73a04f_0_0) for getting started the idea of this project.\n\n\u003e :warning: WARNING: Using this Actions requires set App Settings and Connection String for modifying `SlotSetting` value in settings, because the Azure API doesn't allow us to modifying only `SlotSetting` value without touching actual value of settings, please run this action is non-production first to be make sure everything works as you expected.\n\n## Features\n\n- Prevent unwanted swap app settings between two slots\n- Support multiple Azure App Services\n  - Users can reviews changes all app services app settings before swap\n  - Users can config which the app setting will be swapped or not. \n  - Automatically fix the app setting to be sticked with desired slot following config\n- Leverage GitHub Features\n  - GitHub Action Matrix for retryable steps\n  - GitHub Pull Request review process for protecting unintentionally swap app service. \n\nThis GitHub Actions is required to composition multiple GitHub Actions events for using full workflows as see figure:\n\n![Workflow Composition](docs/images/workflow-composition.png)\n\n## Award\n\nParticipant in the top 10 final round of [Microsoft Virtual Hackathon 2022](https://www.hackerearth.com/challenges/hackathon/microsoft-virtual-hackathon-2022/) June 28, 2022, GitHub Actions Theme\n\n![](./docs/images/ms-virtual-hackathorn-2022-top-10-final-round-cert.png)\n\n\n\n## Example Usage \n\nWrite a full workflows of using this Actions, it requires to using job and specific events for using this GitHub Actions. You can see the example below:\n\n```yaml\nname: Swap Slots\non:\n  workflow_dispatch:\n  pull_request:\n    types: [opened, closed]\n    branches:\n      - appsettings\nenv:\n  config_dir: ./.github/workflows/configs\njobs:\n  get-matrix:\n    runs-on: ubuntu-latest\n    outputs:\n      result: ${{ steps.get-matrix.outputs.deployment-matrix }}\n    steps:\n    - uses: actions/checkout@v3\n    - name: Export deployment matrix\n      id: get-matrix\n      run: |\n        node ./index.js test-get-deploy-slots.json\n      working-directory: ${{ env.config_dir }}\n\n  get-slot-settings:\n    if: github.event_name == 'workflow_dispatch'\n    name: ${{ format('⚙️ Get Slot | {0} - {1}', matrix.name, matrix.slot) }}\n    runs-on: ubuntu-latest\n    needs: [ get-matrix ]\n    strategy:\n      matrix:\n        include: ${{ fromJson(needs.get-matrix.outputs.result) }}\n    steps:\n      - uses: actions/checkout@v2\n      # SP: github action az webapp swap\n      - uses: azure/login@v1\n        with:\n          creds: ${{ secrets.azure_credentials }}\n      - uses: mildronize/actions-az-webapp-swap@v2.0.1\n        with:\n          mode: get-deploy-slots\n          config: ${{ toJson(matrix) }}\n    \n  create-swap-plan: \n    needs: [ get-slot-settings ]\n    name: Create Swap Plan\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n      - name: Create Swap Plan\n        uses: mildronize/actions-az-webapp-swap@v2.0.1\n        with: \n          mode: create-swap-plan\n          token: ${{ secrets.PAT }}\n          repo: mildronize/actions-az-webapp-swap-demo\n          \n  set-slot-settings:\n    if: \u003e- \n      github.event_name == 'pull_request' \u0026\u0026\n      github.event.action != 'closed'\n    name: ${{ format('⚙️ Set Slot | {0} - {1}', matrix.name, matrix.slot) }}\n    needs: [ get-matrix  ]\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        include: ${{ fromJson(needs.get-matrix.outputs.result) }}\n    steps:\n      - uses: actions/checkout@v2\n      # SP: github action az webapp swap\n      - uses: azure/login@v1\n        with:\n          creds: ${{ secrets.azure_credentials }}\n      \n      - name: set-slot-settings\n        uses: mildronize/actions-az-webapp-swap@v2.0.1\n        with: \n          mode: set-deploy-slots\n          config: ${{ toJson(matrix) }}\n\n  swap-slot:\n    if: \u003e- \n      github.event_name == 'pull_request' \u0026\u0026\n      github.event.action == 'closed' \u0026\u0026 \n      github.event.pull_request.merged == true\n    name: ${{ format('🚀 Swap Slot | {0} - {1}', matrix.name, matrix.slot) }}\n    needs: [ get-matrix ]\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        include: ${{ fromJson(needs.get-matrix.outputs.result) }}\n    steps:\n      - uses: actions/checkout@v2\n      # SP: github action az webapp swap\n      - uses: azure/login@v1\n        with:\n          creds: ${{ secrets.azure_credentials }}\n      \n      - name: set-slot-settings\n        uses: mildronize/actions-az-webapp-swap@v2.0.1\n        with: \n          mode: swap-slots\n          config: ${{ toJson(matrix) }}\n\n  clean:\n    name: Clean\n    needs: [ swap-slot ]\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n      - name: clean\n        uses: mildronize/actions-az-webapp-swap@v2.0.1\n        with: \n          mode: clean\n          token: ${{ secrets.PAT }}\n          repo: mildronize/actions-az-webapp-swap-demo\n\n  close:\n    if: \u003e- \n      github.event_name == 'pull_request' \u0026\u0026\n      github.event.action == 'closed' \u0026\u0026 \n      github.event.pull_request.merged == false\n    name: close PR\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n      - name: clean\n        uses: mildronize/actions-az-webapp-swap@v2.0.1\n        with: \n          mode: clean\n          token: ${{ secrets.PAT }}\n          repo: mildronize/actions-az-webapp-swap-demo\n  \n\n```\n\nWrite a JSON config file: \n\n```json\n[\n  {\n    \"name\": \"my-swap-app-test-01\",\n    \"resourceGroup\": \"rg-swap-app-test\",\n    \"slot\": \"staging\",\n    \"targetSlot\": \"production\",\n    \"defaultSlotSetting\": \"true\",\n    \"defaultSensitive\": \"false\",\n    \"appSettings\": [\n      {\n        \"name\": \"data1\",\n        \"sensitive\": false,\n        \"slotSetting\": true\n      },\n      {\n        \"name\": \"data2\",\n        \"sensitive\": false,\n        \"slotSetting\": true\n      },\n      {\n        \"name\": \"data3\",\n        \"sensitive\": true,\n        \"slotSetting\": true\n      }\n    ],\n    \"connectionStrings\": [\n      {\n        \"name\": \"data4\",\n        \"sensitive\": true,\n        \"slotSetting\": true\n      }\n    ]\n  }\n]\n```\n\n## GitHub Tokens \n\n- Assign permission `read:org, repo`\n\n## Create a Service Principle to Access the Azure Resources\n\nTo handle with service principle, I've suggestion 2 ways based on scenarios:\n1. Create a Service Principle and assign Role \u0026 Scope at the same time\n    ```bash\n    az ad sp create-for-rbac --name \"my-test-app\" --role contributor --scopes /subscriptions/9eac942-xxxxxxxxx --sdk-auth\n    ```\n\n    Note: using [azure/login] GitHub Actions require flag `--sdk-auth`, \n    even [this flag is deprecated](https://github.com/marketplace/actions/azure-login#configure-a-service-principal-with-a-secret).\n\n    To login, using `azure/login` Actions, \n    ```yml\n      - uses: azure/login@v1\n        with:\n          creds: ${{ secrets.azure_credentials }}\n    ```\n\n    **Use Case:** This will suite with a few resource to handle\n\n2. Create a Service Principle without assigning any role assignment\n\n    ```bash\n    az ad sp create-for-rbac -n my-test-app --skip-assignment --sdk-auth\n    ```\n\n    Note: using [azure/login] GitHub Actions require flag `--sdk-auth`, even this flag is deprecated.\n\n    To support this service principle, the [azure/login] [support Support for using allow-no-subscriptions](https://github.com/marketplace/actions/azure-login#support-for-using-allow-no-subscriptions-flag-with-az-login)\n\n\n    To login, using `azure/login` Actions, \n    ```yml\n      - uses: azure/login@v1\n        with:\n          creds: ${{ secrets.azure_credentials }}\n          allow-no-subscriptions: true\n    ```\n\n    Next step, you can assign this Service Principle in a Azure AD group, and assign role assignment (Access Control (IAM)) to the resource that you want to get access:\n\n    For example,\n\n    - Assign role assignment (Access Control (IAM)) to the resource that you want to get access:\n    \n        ![](docs/images/access-control.png)\n\n    - Assign this Service Principle in a Azure AD group:\n    \n        ![](docs/images/assign-service-principle-in-group.png)\n\n## Azure Permission \n\nTo provide Least Privilege for Azure Resources: \n\nRead more:\n- [https://dev.to/michaelsrichter/how-to-deploy-to-azure-with-least-privilege-5cjc](https://dev.to/michaelsrichter/how-to-deploy-to-azure-with-least-privilege-5cjc)\n- [Azure Custom Role](https://learn.microsoft.com/en-us/azure/role-based-access-control/custom-roles)\n  - [Create Custom Role by Portal](https://learn.microsoft.com/en-us/azure/role-based-access-control/custom-roles-portal)\n  - [Create Custom Role by CLI](https://learn.microsoft.com/en-us/azure/role-based-access-control/custom-roles-cli)\n\n\n## Create Azure Custom Role for this Actions\n\nCreate a file `role.json` and save the JSON content below:\n\n```json\n{\n    \"type\": \"Microsoft.Authorization/roleDefinitions\",\n    \"roleName\": \"prod-swap-slot\",\n    \"description\": \"Prod Swap Slot\",\n    \"assignableScopes\": [\n        \"/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"\n    ],\n    \"name\": \"prod-swap-slot\",\n    \"roleType\": \"CustomRole\",\n    \"permissions\": [\n        {\n            \"actions\": [\n                \"Microsoft.Web/sites/slots/slotsswap/action\",\n                \"Microsoft.Web/sites/slots/config/list/Action\",\n                \"Microsoft.Web/sites/config/Read\",\n                \"Microsoft.Web/sites/config/list/Action\",\n                \"Microsoft.Web/sites/config/Write\",\n                \"Microsoft.Web/sites/slots/config/Write\",\n                \"microsoft.web/sites/slots/operationresults/read\"\n            ],\n            \"notActions\": [],\n            \"dataActions\": [],\n            \"notDataActions\": []\n        }\n    ]\n}\n```\n\nRun this command to create a role.\n\n```bash\naz role definition create --role-definition role.json\n```\n\n\n### get-deploy-slot\n\n```\nMicrosoft.Web/sites/slots/config/list/Action\nMicrosoft.Web/sites/config/Read\nMicrosoft.Web/sites/config/list/Action\n```\n\n### set-deploy-slot\n\n```\nMicrosoft.Web/sites/config/Write\nMicrosoft.Web/sites/slots/config/Write\n```\n\n### swap-slot\n\n```\nMicrosoft.Web/sites/slots/slotsswap/action\nMicrosoft.Web/sites/slots/operationresults/read\n```\n\n\n## Authors\n\n- Thada Wangthammang ([@mildronize](https://github.com/mildronize))\n- Sirinat Paphatsirinatthi ([@dmakeroam](https://github.com/dmakeroam))\n- Piti Champeethong ([@ninefyi](https://github.com/ninefyi))\n\n# Sponsors\n\n\u003ctable\u003e\n    \u003ctr\u003e\n        \u003ctd style=\"text-align:center;\"\u003e\n            \u003cdiv\u003e\u003ca href=\"https://www.tt-ss.net/\"\u003e\u003cimg src=\"docs/images/sponsor-tt-ss.jpg\" width=\"200\" alt=\"\"\u003e\u003c/a\u003e\u003c/div\u003e\n            \u003ca href=\"https://www.tt-ss.net/\"\u003eT.T. Software Solution\u003c/a\u003e\n        \u003c/td\u003e\n        \u003ctd style=\"text-align:center\"\u003e\n            \u003cdiv\u003e\u003ca href=\"https://www.wrmsoftware.com/\"\u003e\u003cimg src=\"docs/images/sponsor-wrmsoftware.png\" width=\"200\"\n                        alt=\"\"\u003e\u003c/a\u003e\u003c/div\u003e\n            \u003ca href=\"https://www.wrmsoftware.com/\"\u003eWRM Software\u003c/a\u003e\n        \u003c/td\u003e\n    \u003c/tr\u003e\n    \u003c/tbody\u003e\n\u003c/table\u003e\n\n## TODO\n\n- [ ] Mask sensitive in log\n\n\u003c!-- Link --\u003e\n\n[azure/login]: https://github.com/marketplace/actions/azure-login","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmildronize%2Factions-az-webapp-swap","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmildronize%2Factions-az-webapp-swap","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmildronize%2Factions-az-webapp-swap/lists"}