├── .devcontainer └── devcontainer.json ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug.md │ └── quickstart.md ├── acl │ └── access.yml ├── compliance │ └── inventory.yml ├── dependabot.yml ├── policies │ ├── branch-protection.yml │ └── jit.yml └── workflows │ ├── dependency-review.yml │ ├── scorecards.yml │ ├── tf-plan-apply.yml │ └── tf-unit-tests.yml ├── .gitignore ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── SUPPORT.md ├── bootstrap ├── README.md ├── bootstrap.ps1 ├── bootstrap.sh ├── set-local-env.ps1 ├── set-local-env.sh ├── tenant-configuration │ ├── .terraform.lock.hcl │ ├── grant-ppadmin.ps1 │ ├── grant-ppadmin.sh │ ├── main.tf │ ├── outputs.tf │ └── variables.tf └── tf-backend │ ├── tf-backend.bicep │ └── tf-subscription.bicep ├── quickstarts ├── 101-hello-power-platform │ ├── README.md │ ├── README.md.tmpl │ ├── identity │ │ ├── main.tf │ │ ├── output.tf │ │ └── variables.tf │ ├── main.tf │ ├── output.tf │ ├── power-platform │ │ ├── main.tf │ │ ├── output.tf │ │ └── variables.tf │ └── variables.tf ├── 102-github-pipeline │ ├── README.md │ ├── README.md.tmpl │ ├── images │ │ ├── pipeline--2.png │ │ ├── pipeline-1.png │ │ └── plan-output.png │ ├── main.tf │ └── tf-102-example-pipeline.yml ├── 103-demo-tenant │ ├── .gitignore │ ├── README.md │ ├── README.md.tmpl │ ├── assign_license.ps1 │ ├── check_and_create_env.ps1 │ ├── get_graph_access_token.ps1 │ ├── get_license_skus.ps1 │ ├── main.tf │ ├── modules │ │ ├── environments │ │ │ ├── main.tf │ │ │ ├── outputs.tf │ │ │ └── variables.tf │ │ ├── groups │ │ │ ├── main.tf │ │ │ └── variables.tf │ │ ├── licenses │ │ │ ├── main.tf │ │ │ └── variables.tf │ │ └── users │ │ │ ├── main.tf │ │ │ └── variables.tf │ ├── sample.tfvars.txt │ ├── users.json │ └── variables.tf ├── 201-D365-finance-environment │ ├── README.md │ ├── README.md.tmpl │ ├── main.tf │ ├── outputs.tf │ └── variables.tf ├── 300-pac-checkov │ ├── README.md │ ├── README.md.tmpl │ ├── main.tf │ ├── tfcheckov.sh │ └── variables.tf └── 301-sap-gateway │ ├── README.md │ ├── README.md.tmpl │ ├── components.excalidraw │ ├── gateway-principal │ ├── README.md │ ├── README.md.tmpl │ ├── main.tf │ └── outputs.tf │ ├── gateway-vm │ ├── java-runtime-setup │ │ ├── main.tf │ │ ├── outputs.tf │ │ └── variables.tf │ ├── main.tf │ ├── outputs.tf │ ├── ps7-setup │ │ ├── main.tf │ │ ├── outputs.tf │ │ └── variables.tf │ ├── runtime-setup │ │ ├── main.tf │ │ ├── outputs.tf │ │ └── variables.tf │ ├── sapnco-install │ │ ├── main.tf │ │ ├── outputs.tf │ │ └── variables.tf │ └── variables.tf │ ├── img │ └── components.png │ ├── main.tf │ ├── outputs.tf │ ├── storage-account │ ├── main.tf │ ├── outputs.tf │ ├── sapnco │ │ └── README.md │ ├── scripts │ │ ├── java-setup.ps1 │ │ ├── ps7-setup.ps1 │ │ └── runtime-setup.ps1 │ └── variables.tf │ ├── tfcheckov.sh │ ├── tfclean.sh │ ├── tfplan.sh │ └── variables.tf └── tools └── quickstartgen ├── README.md ├── README.md.tmpl ├── go.mod ├── go.sum ├── mainreadme.md.tmpl ├── mainreadmetemplate.md.tmpl ├── quickstart.md.tmpl ├── quickstartgen └── quickstartgen.go /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Terraform", 3 | "image": "mcr.microsoft.com/vscode/devcontainers/base:ubuntu", 4 | "features": { 5 | "ghcr.io/devcontainers/features/go:1": { 6 | "version": "latest" 7 | }, 8 | "ghcr.io/devcontainers/features/terraform": { 9 | "version": "latest" 10 | }, 11 | "ghcr.io/devcontainers/features/azure-cli": { 12 | "version": "latest" 13 | }, 14 | "ghcr.io/devcontainers-contrib/features/gh-cli": { 15 | "version": "latest" 16 | }, 17 | "ghcr.io/devcontainers/features/python:1.7.1" : { 18 | "toolsToinstall": "checkov" 19 | } 20 | }, 21 | "postCreateCommand": "git config --global --add safe.directory /workspaces/power-platform-terraform-quickstarts", 22 | "customizations": { 23 | "vscode": { 24 | "extensions": [ 25 | "hashicorp.terraform", 26 | "github.copilot", 27 | "github.copilot-chat", 28 | "ms-azuretools.vscode-bicep", 29 | "github.vscode-github-actions", 30 | "ms-azuretools.vscode-azureterraform", 31 | "DavidAnson.vscode-markdownlint" 32 | ], 33 | "settings": { 34 | "terraform.languageServer": { 35 | "enabled": true, 36 | "args": [ 37 | "serve" 38 | ] 39 | } 40 | } 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Force the following filetypes to have unix eols, so Windows does not break them 2 | *.* text eol=lf 3 | 4 | 5 | # Exclude common image formats and PDF 6 | *.png -text 7 | *.jpg -text 8 | *.jpeg -text 9 | *.gif -text 10 | *.bmp -text 11 | *.tiff -text 12 | *.tif -text 13 | *.svg -text 14 | *.ico -text 15 | *.webp -text 16 | *.pdf -text -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: 'bug,triage' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Describe the bug 11 | 12 | A clear and concise description of what the bug is. 13 | 14 | ### To Reproduce 15 | 16 | Steps to reproduce the behavior: 17 | 1. Go to '...' 18 | 2. Click on '....' 19 | 3. Scroll down to '....' 20 | 4. See error 21 | 22 | ### Sample Terraform Code 23 | 24 | If applicable, add terraform code to help explain your problem. 25 | 26 | **REMINDER: REMOVE SENSITIVE DATA SUCH AS SECRETS, USER NAMES, EMAILS, TENANT INFORMATION, ETC.** 27 | 28 | ```hcl 29 | 30 | ``` 31 | 32 | ### Expected behavior 33 | 34 | A clear and concise description of what you expected to happen. 35 | 36 | ## System Information 37 | 38 | - Provider Version: [e.g. 0.2.2] 39 | - OS & Version: [e.g. Windows, Linux, MacOS] 40 | 41 | 42 | ## Additional context 43 | Add any other context about the problem here. 44 | 45 | ## Contribution 46 | 47 | ## Contribution 48 | Do you plan to raise a PR to address this issue? YES / NO? -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/quickstart.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Quickstart Example Request" 3 | about: "Request a new quickstart example to be added to the Terraform provider repository." 4 | labels: [quickstart,triage] 5 | assignees: "" 6 | 7 | --- 8 | 9 | ## Description 10 | 11 | 12 | 13 | ### Example Details 14 | 15 | - Proposed Name: (e.g. 101-my-example) 16 | - Supporting documentation: 17 | - Estimated complexity/effort: 18 | - Related resources/data sources: 19 | 20 | ### Potential Terraform Configuration 21 | 22 | ```hcl 23 | # Sample Terraform config that describes how the new resource might look. 24 | 25 | data "powerplatform_[your data source name]" "example_data_source" { 26 | name = "example" 27 | parameter1 = "value1" 28 | parameter2 = "value2" 29 | } 30 | 31 | ``` 32 | 33 | ## Definition of Done 34 | 35 | - [ ] Example in the /quickstarts folder 36 | - [ ] Example documentation in README.md.tmpl 37 | - [ ] Updated auto-generated provider docs with `make quickstarts` 38 | - [ ] Confirmation that you have manually tested this 39 | 40 | ## Contributions 41 | 42 | Do you plan to raise a PR to address this issue? YES / NO? 43 | 44 | See the [contributing guide](/CONTRIBUTING.md?) for more information about what's expected for contributions. -------------------------------------------------------------------------------- /.github/acl/access.yml: -------------------------------------------------------------------------------- 1 | # Documentation for ACL policy: https://aka.ms/gim/docs/policy/acl 2 | 3 | name: Access control list 4 | description: List of teams and their permission levels 5 | resource: repository 6 | where: 7 | configuration: 8 | manageAccess: 9 | - member: mdotson 10 | role: Maintain 11 | - member: leightami 12 | role: Maintain 13 | - team: crewLightningBolt 14 | role: Push -------------------------------------------------------------------------------- /.github/compliance/inventory.yml: -------------------------------------------------------------------------------- 1 | inventory: 2 | - source: DirectOwners 3 | items: 4 | - id: mdotson@microsoft.com 5 | - id: leightami@microsoft.com 6 | isProduction: false 7 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gomod" # See documentation for possible values 9 | directory: "/tools/quickstartgen" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | - package-ecosystem: "terraform" # See documentation for possible values 13 | directory: "/quickstarts/101-hello-power-platform" # Location of package manifests 14 | schedule: 15 | interval: "daily" 16 | - package-ecosystem: "terraform" # See documentation for possible values 17 | directory: "/quickstarts/102-github-pipeline" # Location of package manifests 18 | schedule: 19 | interval: "daily" 20 | - package-ecosystem: "terraform" # See documentation for possible values 21 | directory: "/quickstarts/103-power-pipelines" # Location of package manifests 22 | schedule: 23 | interval: "daily" 24 | - package-ecosystem: "terraform" # See documentation for possible values 25 | directory: "/quickstarts/201-D365-finance-environment" # Location of package manifests 26 | schedule: 27 | interval: "daily" 28 | - package-ecosystem: "terraform" # See documentation for possible values 29 | directory: "/quickstarts/300-pac-checkov" # Location of package manifests 30 | schedule: 31 | interval: "daily" 32 | - package-ecosystem: "terraform" # See documentation for possible values 33 | directory: "/bootstrap/tenant-configuration" # Location of package manifests 34 | schedule: 35 | interval: "daily" 36 | - package-ecosystem: "github-actions" # See documentation for possible values 37 | directory: "/.github/workflows" # Location of package manifests 38 | schedule: 39 | interval: "daily" 40 | - package-ecosystem: "devcontainers" # See documentation for possible values 41 | directory: "/" 42 | schedule: 43 | interval: "daily" 44 | 45 | - package-ecosystem: github-actions 46 | directory: / 47 | schedule: 48 | interval: daily 49 | -------------------------------------------------------------------------------- /.github/policies/branch-protection.yml: -------------------------------------------------------------------------------- 1 | # Documentation for branch policy: https://aka.ms/gim/docs/policy/branch-protection 2 | 3 | name: Default branch protection policy 4 | description: Requires one reviewer for merges into main branch 5 | resource: repository 6 | where: 7 | configuration: 8 | branchProtectionRules: 9 | - branchNamePattern: "main" 10 | requiredApprovingReviewsCount: 1 11 | dismissStaleReviews: true 12 | -------------------------------------------------------------------------------- /.github/policies/jit.yml: -------------------------------------------------------------------------------- 1 | # Documentation for JIT policy: https://aka.ms/gim/docs/policy/jit 2 | 3 | # metadata 4 | id: id 5 | name: JIT_Access 6 | description: Policy for admin JIT for repos in this org 7 | 8 | # filters 9 | resource: repository 10 | 11 | # primitive configuration 12 | configuration: 13 | jitAccess: 14 | enabled: true 15 | maxHours: 2 16 | approvers: 17 | role: Maintain 18 | requestors: 19 | role: Write 20 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | # Dependency Review Action 2 | # 3 | # This Action will scan dependency manifest files that change as part of a Pull Request, 4 | # surfacing known-vulnerable versions of the packages declared or updated in the PR. 5 | # Once installed, if the workflow run is marked as required, 6 | # PRs introducing known-vulnerable packages will be blocked from merging. 7 | # 8 | # Source repository: https://github.com/actions/dependency-review-action 9 | name: 'Dependency Review' 10 | on: [pull_request] 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | dependency-review: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Harden the runner (Audit all outbound calls) 20 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 21 | with: 22 | egress-policy: audit 23 | 24 | - name: 'Checkout Repository' 25 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 26 | - name: 'Dependency Review' 27 | uses: actions/dependency-review-action@da24556b548a50705dd671f47852072ea4c105d9 # v4 28 | -------------------------------------------------------------------------------- /.github/workflows/scorecards.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. They are provided 2 | # by a third-party and are governed by separate terms of service, privacy 3 | # policy, and support documentation. 4 | 5 | name: Scorecard supply-chain security 6 | on: 7 | # For Branch-Protection check. Only the default branch is supported. See 8 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection 9 | branch_protection_rule: 10 | # To guarantee Maintained check is occasionally updated. See 11 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained 12 | schedule: 13 | - cron: '20 7 * * 2' 14 | push: 15 | branches: ["main"] 16 | 17 | # Declare default permissions as read only. 18 | permissions: read-all 19 | 20 | jobs: 21 | analysis: 22 | name: Scorecard analysis 23 | runs-on: ubuntu-latest 24 | permissions: 25 | # Needed to upload the results to code-scanning dashboard. 26 | security-events: write 27 | # Needed to publish results and get a badge (see publish_results below). 28 | id-token: write 29 | contents: read 30 | actions: read 31 | # To allow GraphQL ListCommits to work 32 | issues: read 33 | pull-requests: read 34 | # To detect SAST tools 35 | checks: read 36 | 37 | steps: 38 | - name: Harden the runner (Audit all outbound calls) 39 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 40 | with: 41 | egress-policy: audit 42 | 43 | - name: "Checkout code" 44 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 45 | with: 46 | persist-credentials: false 47 | 48 | - name: "Run analysis" 49 | uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1 50 | with: 51 | results_file: results.sarif 52 | results_format: sarif 53 | # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: 54 | # - you want to enable the Branch-Protection check on a *public* repository, or 55 | # - you are installing Scorecards on a *private* repository 56 | # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. 57 | # repo_token: ${{ secrets.SCORECARD_TOKEN }} 58 | 59 | # Public repositories: 60 | # - Publish results to OpenSSF REST API for easy access by consumers 61 | # - Allows the repository to include the Scorecard badge. 62 | # - See https://github.com/ossf/scorecard-action#publishing-results. 63 | # For private repositories: 64 | # - `publish_results` will always be set to `false`, regardless 65 | # of the value entered here. 66 | publish_results: true 67 | 68 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF 69 | # format to the repository Actions tab. 70 | - name: "Upload artifact" 71 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 72 | with: 73 | name: SARIF file 74 | path: results.sarif 75 | retention-days: 5 76 | 77 | # Upload the results to GitHub's code scanning dashboard. 78 | - name: "Upload to code-scanning" 79 | uses: github/codeql-action/upload-sarif@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 80 | with: 81 | sarif_file: results.sarif 82 | -------------------------------------------------------------------------------- /.github/workflows/tf-plan-apply.yml: -------------------------------------------------------------------------------- 1 | name: 'Terraform Plan/Apply' 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | #Special permissions required for OIDC authentication 12 | permissions: 13 | id-token: write 14 | contents: read 15 | pull-requests: write 16 | 17 | #These environment variables are used by the terraform azure provider to setup OIDD authenticate. 18 | env: 19 | ARM_CLIENT_ID: "${{ vars.PPADMIN_CLIENT_ID }}" 20 | ARM_CLIENT_SECRET: "${{ secrets.PPADMIN_CLIENT_SECRET }}" 21 | ARM_TENANT_ID: "${{ vars.PPADMIN_TENANT_ID }}" 22 | ARM_SUBSCRIPTION_ID: "${{ vars.PPADMIN_SUBSCRIPTION_ID }}" 23 | TF_STATE_STORAGE_ACCOUNT_NAME: ${{ vars.TF_STATE_STORAGE_ACCOUNT_NAME }} 24 | TF_STATE_RESOURCE_GROUP_NAME: ${{ vars.TF_STATE_RESOURCE_GROUP_NAME }} 25 | POWER_PLATFORM_CLIENT_ID: ${{ vars.PPADMIN_CLIENT_ID }} 26 | POWER_PLATFORM_SECRET: ${{ secrets.PPADMIN_CLIENT_SECRET }} 27 | POWER_PLATFORM_TENANT_ID: ${{ vars.PPADMIN_TENANT_ID }} 28 | TF_VAR_azure_subscription_id: "${{ vars.PPADMIN_SUBSCRIPTION_ID }}" 29 | TARGET_DIR: ${{ github.workspace }}/quickstarts/101-hello-power-platform 30 | 31 | jobs: 32 | terraform-plan: 33 | name: 'Terraform Plan' 34 | runs-on: ubuntu-latest 35 | env: 36 | #this is needed since we are running terraform with read-only permissions 37 | ARM_SKIP_PROVIDER_REGISTRATION: true 38 | outputs: 39 | tfplanExitCode: ${{ steps.tf-plan.outputs.exitcode }} 40 | 41 | steps: 42 | # Checkout the repository to the GitHub Actions runner 43 | - name: Harden the runner (Audit all outbound calls) 44 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 45 | with: 46 | egress-policy: audit 47 | 48 | - name: Checkout 49 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 50 | 51 | # Install the latest version of the Terraform CLI 52 | - name: Setup Terraform 53 | uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2 54 | with: 55 | terraform_wrapper: false 56 | 57 | # Initialize a new or existing Terraform working directory by creating initial files, loading any remote state, downloading modules, etc. 58 | - name: Terraform Init 59 | run: terraform -chdir=$TARGET_DIR init -backend-config="storage_account_name=$TF_STATE_STORAGE_ACCOUNT_NAME" -backend-config="resource_group_name=$TF_STATE_RESOURCE_GROUP_NAME" 60 | 61 | # Checks that all Terraform configuration files adhere to a canonical format 62 | # Will fail the build if not 63 | - name: Terraform Format 64 | run: terraform -chdir=$TARGET_DIR fmt -check 65 | 66 | # Generates an execution plan for Terraform 67 | # An exit code of 0 indicated no changes, 1 a terraform failure, 2 there are pending changes. 68 | - name: Terraform Plan 69 | id: tf-plan 70 | run: | 71 | export exitcode=0 72 | terraform -chdir=$TARGET_DIR plan -detailed-exitcode -no-color -out tfplan || export exitcode=$? 73 | 74 | echo "exitcode=$exitcode" >> $GITHUB_OUTPUT 75 | 76 | if [ $exitcode -eq 1 ]; then 77 | echo Terraform Plan Failed! 78 | exit 1 79 | else 80 | exit 0 81 | fi 82 | 83 | # Save plan to artifacts 84 | - name: Publish Terraform Plan 85 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 86 | with: 87 | name: tfplan 88 | path: ${{ env.TARGET_DIR }}/tfplan 89 | 90 | # Create string output of Terraform Plan 91 | - name: Create String Output 92 | id: tf-plan-string 93 | run: | 94 | TERRAFORM_PLAN=$(terraform -chdir=$TARGET_DIR show -no-color tfplan) 95 | 96 | delimiter="$(openssl rand -hex 8)" 97 | echo "summary<<${delimiter}" >> $GITHUB_OUTPUT 98 | echo "## Terraform Plan Output" >> $GITHUB_OUTPUT 99 | echo "
Click to expand" >> $GITHUB_OUTPUT 100 | echo "" >> $GITHUB_OUTPUT 101 | echo '```terraform' >> $GITHUB_OUTPUT 102 | echo "$TERRAFORM_PLAN" >> $GITHUB_OUTPUT 103 | echo '```' >> $GITHUB_OUTPUT 104 | echo "
" >> $GITHUB_OUTPUT 105 | echo "${delimiter}" >> $GITHUB_OUTPUT 106 | 107 | # Publish Terraform Plan as task summary 108 | - name: Publish Terraform Plan to Task Summary 109 | env: 110 | SUMMARY: ${{ steps.tf-plan-string.outputs.summary }} 111 | run: | 112 | echo "$SUMMARY" >> $GITHUB_STEP_SUMMARY 113 | 114 | # If this is a PR post the changes 115 | - name: Push Terraform Output to PR 116 | if: github.ref != 'refs/heads/main' 117 | uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 118 | env: 119 | SUMMARY: "${{ steps.tf-plan-string.outputs.summary }}" 120 | with: 121 | github-token: ${{ secrets.GITHUB_TOKEN }} 122 | script: | 123 | const body = `${process.env.SUMMARY}`; 124 | github.rest.issues.createComment({ 125 | issue_number: context.issue.number, 126 | owner: context.repo.owner, 127 | repo: context.repo.repo, 128 | body: body 129 | }) 130 | 131 | terraform-apply: 132 | name: 'Terraform Apply' 133 | if: github.ref == 'refs/heads/main' && needs.terraform-plan.outputs.tfplanExitCode == 2 134 | runs-on: ubuntu-latest 135 | environment: production 136 | needs: [terraform-plan] 137 | 138 | steps: 139 | # Checkout the repository to the GitHub Actions runner 140 | - name: Harden the runner (Audit all outbound calls) 141 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 142 | with: 143 | egress-policy: audit 144 | 145 | - name: Checkout 146 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 147 | 148 | # Install the latest version of Terraform CLI and configure the Terraform CLI configuration file with a Terraform Cloud user API token 149 | - name: Setup Terraform 150 | uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2 151 | 152 | # Initialize a new or existing Terraform working directory by creating initial files, loading any remote state, downloading modules, etc. 153 | - name: Terraform Init 154 | run: terraform -chdir=$TARGET_DIR init -backend-config="storage_account_name=$TF_STATE_STORAGE_ACCOUNT_NAME" -backend-config="resource_group_name=$TF_STATE_RESOURCE_GROUP_NAME" 155 | 156 | # Download saved plan from artifacts 157 | - name: Download Terraform Plan 158 | uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 159 | with: 160 | name: tfplan 161 | path: ${{ env.TARGET_DIR }} 162 | 163 | # Terraform Apply 164 | - name: Terraform Apply 165 | run: terraform -chdir=$TARGET_DIR apply -auto-approve tfplan -------------------------------------------------------------------------------- /.github/workflows/tf-unit-tests.yml: -------------------------------------------------------------------------------- 1 | name: 'Terraform Unit Tests' 2 | 3 | on: 4 | push: 5 | 6 | permissions: 7 | contents: read 8 | 9 | jobs: 10 | terraform-unit-tests: 11 | name: 'Terraform Unit Tests' 12 | runs-on: ubuntu-latest 13 | permissions: 14 | security-events: write 15 | contents: read 16 | actions: read 17 | steps: 18 | # Checkout the repository to the GitHub Actions runner 19 | - name: Harden the runner (Audit all outbound calls) 20 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 21 | with: 22 | egress-policy: audit 23 | 24 | - name: Checkout 25 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 26 | 27 | # Install the latest version of Terraform CLI and configure the Terraform CLI configuration file with a Terraform Cloud user API token 28 | - name: Setup Terraform 29 | uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2 30 | 31 | # Initialize a new or existing Terraform working directory by creating initial files, loading any remote state, downloading modules, etc. 32 | - name: Terraform Init 33 | run: terraform init -backend=false 34 | 35 | # Validate terraform files 36 | - name: Terraform Validate 37 | run: terraform validate 38 | 39 | # Checks that all Terraform configuration files adhere to a canonical format 40 | - name: Terraform Format 41 | run: terraform fmt -recursive 42 | 43 | # Perform a security scan of the terraform code using checkov 44 | - name: Run Checkov action 45 | id: checkov 46 | uses: bridgecrewio/checkov-action@360818f2ad44468d3294cfddae854a8c9036dfee # master 47 | with: 48 | framework: terraform 49 | 50 | # Upload results to GitHub Advanced Security 51 | - name: Upload SARIF file 52 | if: success() || failure() 53 | uses: github/codeql-action/upload-sarif@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 54 | with: 55 | sarif_file: results.sarif 56 | category: checkov 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # .tfstate files 5 | *.tfstate 6 | *.tfstate.* 7 | *.lock.hcl 8 | *.hcl 9 | 10 | # Crash log files 11 | crash.log 12 | crash.*.log 13 | 14 | # Exclude all .tfvars files, which are likely to contain sensitive data, such as 15 | # password, private keys, and other secrets. These should not be part of version 16 | # control as they are data points which are potentially sensitive and subject 17 | # to change depending on the environment. 18 | *.tfvars 19 | *.tfvars.json 20 | 21 | # Ignore override files as they are usually used to override resources locally and so 22 | # are not checked in 23 | override.tf 24 | override.tf.json 25 | *_override.tf 26 | *_override.tf.json 27 | 28 | # Include override files you do wish to add to version control using negated pattern 29 | # !example_override.tf 30 | 31 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 32 | # example: *tfplan* 33 | 34 | # Ignore CLI configuration files 35 | .terraformrc 36 | terraform.rc 37 | 38 | # Terraform plan files 39 | tf.json 40 | tf.plan 41 | *.plan 42 | *.tfplan 43 | 44 | # Terraform state files 45 | *.tfstate 46 | 47 | #Terraform lock files 48 | *.lock.hcl 49 | *.terraform.lock.hcl 50 | .terraform.lock.hcl 51 | terraform.locl.hcl 52 | 53 | # Checov Scripts 54 | tfchecov.sh 55 | *.locl.hcl 56 | 57 | # SAP installation files 58 | sapnco.exe 59 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # These owners will be the default owners for everything in 2 | # the repo. Unless a later match takes precedence, 3 | # @global-owner1 and @global-owner2 will be requested for 4 | # review when someone opens a pull request. 5 | 6 | * @mawasile @mattdot @leighatami @ianjensenisme @polatengin @eduardodfmex -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 4 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 5 | the rights to use your contribution. For details, visit . 6 | 7 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 8 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 9 | provided by the bot. You will only need to do this once across all repos using our CLA. 10 | 11 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 12 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 13 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Power Platform Terraform Quickstarts 3 | 4 | This repository contains example terraform modules that can be used to quickly deploy Power Platform environments and other Azure resources. The modules are intended to demonstrate some common scenarios where managing Power Platform resources along side Azure, [Entra](https://entra.microsoft.com), or other resources can be facilitated with the [Terraform Provider for Power Platform](https://github.com/microsoft/terraform-provider-power-platform). The modules are examples and are not intended to be used in production environments without modification. 5 | 6 | This repository contains scripts quickly build out a new tenant and configure it to allow you to deploy and manage Power Platform environments along side other Azure resources. The scripts assume that you are starting with a completely new tenant with an empty Azure subscription. This is a template repository that is intended to let you fork and customize the Power Platform/Azure resources to accommodate your own needs. 7 | 8 | ## Prerequisites 9 | 10 | * Microsoft Tenant that you have `global admin` or `user administrator` permissions in 11 | * Azure subscription in the tenant that you have `owner` permissions in 12 | * A fork of this GitHub repository that you have `admin` permissions in 13 | 14 | ### Tooling 15 | 16 | > [!NOTE] 17 | > The following tooling is pre-installed in the [VS Code Dev Container](https://code.visualstudio.com/docs/devcontainers/containers) and it is highly recommended that you use the Dev Container to run the scripts and terraform modules in this repository: 18 | 19 | * [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/) 20 | * [Terraform CLI](https://developer.hashicorp.com/terraform/cli) 21 | * [GitHub CLI](https://cli.github.com/) 22 | * [Docker](https://www.docker.com/) 23 | 24 | ### Terraform Providers 25 | 26 | The following terraform providers are used in this repository: 27 | 28 | * [Power-Platform](https://github.com/microsoft/terraform-provider-power-platform) 29 | * [AzureRM](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs) 30 | * [AzureAD](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs) 31 | * [GitHub](https://registry.terraform.io/providers/integrations/github/latest/docs) 32 | * [Random](https://registry.terraform.io/providers/hashicorp/random/latest/docs) 33 | * [Null](https://registry.terraform.io/providers/hashicorp/null/latest/docs) 34 | 35 | ## Getting Started 36 | 37 | The example terraform modules are intended to be run by GitHub Actions, however there are several steps that need to be run locally by an administrator in order to create the resources the terraform modules need to use. The following steps should be run in order: 38 | 39 | 1. [Bootstrap](bootstrap/README.md) this will create and configure the prerequisites that are needed to run the quickstart examples. 40 | 2. Try out any of the following **Quickstart Examples** 41 | 42 | ## Quickstart Examples 43 | * [300-pac-checkov](./quickstarts/300-pac-checkov/README.md) 44 | * [201-D365-finance-environment](./quickstarts/201-D365-finance-environment/README.md) 45 | * [101-hello-power-platform](./quickstarts/101-hello-power-platform/README.md) 46 | * [301-sap-gateway](./quickstarts/301-sap-gateway/README.md) 47 | * [103-demo-tenant](./quickstarts/103-demo-tenant/README.md) 48 | * [102-github-pipeline](./quickstarts/102-github-pipeline/README.md) 49 | 50 | Quickstarts use the Power-Platform provider, documentation can be found [here](https://microsoft.github.io/terraform-provider-power-platform/). 51 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet) and [Xamarin](https://github.com/xamarin). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/security.md/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/security.md/msrc/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/security.md/msrc/pgp). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/security.md/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/security.md/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | ## How to file issues and get help 4 | 5 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing 6 | issues before filing new issues to avoid duplicates. For new issues, file your bug or 7 | feature request as a new Issue. 8 | 9 | ## Microsoft Support Policy 10 | 11 | Support for this **PROJECT** is limited to the resources listed above. 12 | -------------------------------------------------------------------------------- /bootstrap/README.md: -------------------------------------------------------------------------------- 1 | # Initial Bootstrap Configuration 2 | 3 | This directory contains scripts to bootstrap the initial configuration for using the Power Platform Terraform Provider and **Quickstart Examples**. You have following boostrap options to choose from: 4 | 5 | * Having terraform state saved locally (simplified setup for learning and evaluation, but not recommended for production usage) 6 | * Having terraform state saved in Azure Storage Account (advanced setup which more closely mimics production configuration and requires an Azure Subscription) 7 | 8 | More information regarding terraform state can be found here: 9 | 10 | * 11 | * 12 | 13 | ## Resources Created 14 | 15 | The following resources are created by the `bootstrap.sh` script: 16 | 17 | ### Identity and Access Management 18 | 19 | This is a list of all the required permissions for the app registration / service principal. The service principal can be used to manage Power Platform resources. 20 | An API for this app registration will also be exposed in order to use [Azure CLI as authentication harness](https://github.com/microsoft/terraform-provider-power-platform/blob/main/docs/cli.md) for your Terraform modules. 21 | 22 | * An app registration and service principal for managing Power Platform resources 23 | * Dynamics CRM 24 | * user_impersonation 25 | * Power App Service 26 | * User 27 | * Permissions for the service principal 28 | * AppManagement.ApplicationPackages.Install 29 | * AppManagement.ApplicationPackages.Read 30 | * Licensing.BillingPolicies.Read 31 | * Licensing.BillingPolicies.ReadWrite 32 | * [Power Platform Admin role grant to the service principal](https://learn.microsoft.com/en-us/power-platform/admin/powerplatform-api-create-service-principal#registering-an-admin-management-application) 33 | * Azure Data Access Blob Contributor role grant to the service principal 34 | 35 | ### Optional: Terraform State Backend in Azure Storage Account (advanced version) 36 | 37 | > [!NOTE] 38 | > Storing state in an Azure Storage Account is optional for using the QuickStarts. These configuration options are provided here to allow the examples to more closely mimic production deployments but are not fully hardened for use in production as is. 39 | 40 | * Azure resource group for terraform state 41 | * Azure storage account for terraform state 42 | * Azure blob storage container for terraform state (public access disabled) 43 | * Azure Defender for Storage enabled on the storage account 44 | 45 | ## Prerequisites 46 | 47 | It is highly recommended that you use the Dev Container to run the bootstrap script. The following tooling is pre-installed in the Dev Container: 48 | 49 | * [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/) 50 | * [Terraform CLI](https://developer.hashicorp.com/terraform/cli) 51 | * [GitHub CLI](https://cli.github.com/) 52 | 53 | ## Usage 54 | 55 | The `bootstrap.sh` script is intended to be run locally by a user with `Owner` permissions in the Azure subscription, `Global Administrator` or `User Administrator` permissions in the Azure AD tenant. 56 | 57 | ### Simple version with local Terraform state 58 | 59 | ```bash 60 | ./bootstrap/bootstrap.sh 61 | ``` 62 | 63 | ### Advanced version with Azure Storage Account as Terraform state backend 64 | 65 | ```bash 66 | ./bootstrap/bootstrap.sh --subscription_id --location eastus 67 | ``` 68 | 69 | > [!NOTE] 70 | > Remember that the administrator has to grant permissions to the newly created service principal. The service principal will be created in the same tenant as the subscription. 71 | 72 | #### Outputs (Advanced version only) 73 | 74 | The `bootstrap.sh` [bootstrap.sh](/bootstrap/bootstrap.sh) writes its outputs to a `backend.tfvars` file in the [tenant-configuration](/bootstrap/tenant-configuration/) directory. The `backend.tfvars` file is used by the [tenant-configuration](/bootstrap/tenant-configuration/) terraform configuration to configure the backend for the terraform state. 75 | 76 | ## Next Steps 77 | 78 | ### Log In 79 | 80 | After running `./bootstrap.sh` you can use the following command to login as a user 81 | 82 | ```bash 83 | az login --allow-no-subscriptions --scope api://power-platform_provider_terraform/.default 84 | ``` 85 | 86 | If you want to run the examples as a service principal, see the [Authenticating to Power Platform](https://microsoft.github.io/terraform-provider-power-platform/#authenticating-to-power-platform) section of the Power Platform Terraform Provider documenation for more authentication options. -------------------------------------------------------------------------------- /bootstrap/bootstrap.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [string]$subscription_id, 3 | [string]$location 4 | ) 5 | 6 | $terrformPath = "terraform" 7 | 8 | try { 9 | terraform --version 10 | Write-Output "Terraform is installed and available in the PATH." 11 | } catch { 12 | $terrformPath = Join-Path -Path $env:ProgramData -ChildPath "Terraform\terraform.exe" 13 | if ( -not (Test-Path $terrformPath) ) { 14 | Write-Error "Unable to find terraform" 15 | exit 16 | } 17 | } 18 | 19 | if ($PSCmdlet.MyInvocation.BoundParameters.Count -eq 2) { 20 | if (-not $subscription_id) { 21 | Write-Host "Subscription ID is required. Please pass it in the form of --subscription_id 234..." 22 | exit 1 23 | } 24 | 25 | if (-not $location) { 26 | Write-Host "Location is required. Please pass it in the form of --location WestUS" 27 | exit 1 28 | } 29 | 30 | az login --use-device-code 31 | az account set --subscription $subscription_id 32 | 33 | Write-Host "Using the following subscription:" 34 | az account show 35 | 36 | Write-Host "Installing Bicep..." 37 | az bicep install 38 | az bicep version 39 | 40 | Write-Host "Deploying Terraform Backend resources..." 41 | $deploymentJson = az deployment sub create --location $location --template-file ./tf-backend/tf-subscription.bicep 42 | $deploymentJson | ConvertFrom-Json | Select-Object -ExpandProperty properties | Select-Object -ExpandProperty outputs | ForEach-Object { "$($_.Key) = '$($_.Value.value)'" } | Out-File -FilePath ../backend.tfvars -Encoding utf8 43 | 44 | New-Item -ItemType File -Path ./bootstrap_backend.tfvars -Force 45 | Get-Content ../backend.tfvars | Out-File -FilePath ./tenant-configuration/bootstrap_backend.tfvars -Encoding utf8 46 | Add-Content -Path ./tenant-configuration/bootstrap_backend.tfvars -Value "container_name = 'tfstate'" 47 | Add-Content -Path ./tenant-configuration/bootstrap_backend.tfvars -Value "key = 'tenant-configuration.terraform.tfstate'" 48 | 49 | Write-Host "Terraform backend configuration has been written to ../backend.tfvars" 50 | Get-Content ../backend.tfvars 51 | 52 | Push-Location ./tenant-configuration 53 | 54 | # Enable AzureRM backend in the main.tf file 55 | (Get-Content main.tf) -replace '^[\s]*#backend "azurerm" {}', 'backend "azurerm" {}' | Set-Content main.tf 56 | 57 | Write-Host "Terraform init..." 58 | # Call terraform init with backend.tfvars as backend config file 59 | $env:TF_IN_AUTOMATION = 1 60 | & $terrformPath init -backend-config=./bootstrap_backend.tfvars 61 | 62 | Write-Host "Terraform apply..." 63 | # Run terraform apply and get the output values into variables 64 | & $terrformPath apply --auto-approve -var-file=./bootstrap_backend.tfvars 65 | 66 | } elseif ($PSCmdlet.MyInvocation.BoundParameters.Count -eq 0) { 67 | az login --allow-no-subscriptions --use-device-code 68 | Push-Location ./tenant-configuration 69 | 70 | # Disable AzureRM backend in the main.tf file 71 | (Get-Content main.tf) -replace '^\s*backend "azurerm" \{\}', '#backend "azurerm" {}' | Set-Content main.tf 72 | 73 | Write-Host "Terraform init..." 74 | # Call terraform init with backend.tfvars as backend config file 75 | $env:TF_IN_AUTOMATION = 1 76 | & $terrformPath init 77 | 78 | Write-Host "Terraform apply..." 79 | # Run terraform apply and get the output values into variables 80 | & $terrformPath apply --auto-approve 81 | 82 | } else { 83 | Write-Host "Invalid number of arguments. Please pass either 0 or 2 arguments." 84 | exit 1 85 | } 86 | 87 | $env:POWER_PLATFORM_CLIENT_ID = (& $terrformPath show -json | ConvertFrom-Json).values.outputs.client_id.value 88 | $env:POWER_PLATFORM_SECRET = (& $terrformPath show -json | ConvertFrom-Json).values.outputs.client_secret.value 89 | $env:POWER_PLATFORM_TENANT_ID = (& $terrformPath show -json | ConvertFrom-Json).values.outputs.tenant_id.value 90 | 91 | Pop-Location 92 | 93 | Write-Host "Bootstrap complete!" 94 | Write-Host "" 95 | Write-Host "You have to grant permissions to the new 'Power Platform Admin Service' service principal in the Azure portal to access the Power Platform resources." 96 | Write-Host "" 97 | Write-Host "You now have following options to login to the Power Platform in Terraform:" 98 | Write-Host "1. Use the following environment variables that can be set using /bootstrap/set-local-env.sh:" 99 | Write-Host " provider 'powerplatform' {" 100 | Write-Host " }" 101 | Write-Host "" 102 | Write-Host "2. Use the client_id, client_secret and tenant_id directly:" 103 | Write-Host " provider 'powerplatform' {" 104 | Write-Host " client_id = var.client_id" 105 | Write-Host " client_secret = var.client_secret" 106 | Write-Host " tenant_id = var.tenant_id" 107 | Write-Host " }" 108 | Write-Host "" 109 | Write-Host "3. Use Azure CLI to login that will be used in the provider block:" 110 | Write-Host " For login use: az login --allow-no-subscriptions --scope api://power-platform_provider_terraform/.default" 111 | Write-Host "" 112 | Write-Host " provider 'powerplatform' {" 113 | Write-Host " use_cli = true" 114 | Write-Host " }" 115 | -------------------------------------------------------------------------------- /bootstrap/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$#" -eq 4 ]; then 4 | while [[ $# -gt 0 ]]; do 5 | key="$1" 6 | case $key in 7 | --subscription_id) 8 | subscription_id="$2" 9 | shift # past argument 10 | shift # past value 11 | ;; 12 | --location) 13 | location="$2" 14 | shift # past argument 15 | shift # past value 16 | ;; 17 | # --github_pat_token) 18 | # github_pat_token="$2" 19 | # shift # past argument 20 | # shift # past value 21 | # ;; 22 | *) # unknown option 23 | echo "Unknown option: $key" 24 | exit 1 25 | ;; 26 | esac 27 | done 28 | 29 | if [ -z "$subscription_id" ]; then 30 | echo "Subscription ID is required. Please pass it in the form of --subscription_id 234..." 31 | exit 1 32 | fi 33 | 34 | if [ -z "$location" ]; then 35 | echo "Location is required. Please pass it in the form of --location WestUS" 36 | exit 1 37 | fi 38 | 39 | az login --use-device-code 40 | az account set --subscription $subscription_id 41 | 42 | echo "Using the following subscription:" 43 | az account show 44 | 45 | echo "Installing Bicep..." 46 | az bicep install 47 | az bicep version 48 | 49 | echo "Deploying Terraform Backend resources..." 50 | deploymentJson=$(az deployment sub create --location $location --template-file ./tf-backend/tf-subscription.bicep) 51 | echo $deploymentJson | jq -r '.properties.outputs | to_entries | map("\(.key) = \"\(.value.value)\"") | .[]' > ../backend.tfvars 52 | 53 | touch ./bootstrap_backend.tfvars 54 | cat ../backend.tfvars > ./tenant-configuration/bootstrap_backend.tfvars 55 | echo "container_name = \"tfstate\"" >> ./tenant-configuration/bootstrap_backend.tfvars 56 | echo "key = \"tenant-configuration.terraform.tfstate\"" >> ./tenant-configuration/bootstrap_backend.tfvars 57 | 58 | echo "Terraform backend configuration has been written to ../backend.tfvars" 59 | cat ../backend.tfvars 60 | 61 | pushd ./tenant-configuration 62 | 63 | # Enable AzureRM backend in the main.tf file 64 | sed -i 's/^[[:space:]]*#backend "azurerm" {}/backend "azurerm" {}/' main.tf 65 | 66 | echo "Terraform init..." 67 | # Call terraform init with backend.tfvars as backend config file 68 | TF_IN_AUTOMATION=1 terraform init -backend-config=./bootstrap_backend.tfvars 69 | 70 | echo "Terraform apply..." 71 | # Run terraform apply and get the output values into variables 72 | TF_IN_AUTOMATION=1 terraform apply --auto-approve -var-file=./bootstrap_backend.tfvars 73 | 74 | elif [ "$#" -eq 0 ]; then 75 | az login --allow-no-subscriptions --use-device-code 76 | pushd ./tenant-configuration 77 | 78 | # Disable AzureRM backend in the main.tf file 79 | sed -i 's/^[[:space:]]*backend "azurerm" {}/#backend "azurerm" {}/' main.tf 80 | 81 | echo "Terraform init..." 82 | # Call terraform init with backend.tfvars as backend config file 83 | TF_IN_AUTOMATION=1 terraform init 84 | 85 | echo "Terraform apply..." 86 | # Run terraform apply and get the output values into variables 87 | TF_IN_AUTOMATION=1 terraform apply --auto-approve 88 | 89 | else 90 | echo "Invalid number of arguments. Please pass either 0 or 4 arguments." 91 | exit 1 92 | fi 93 | 94 | export POWER_PLATFORM_CLIENT_ID=$(terraform show -json | jq -r '.values.outputs.client_id.value') 95 | export POWER_PLATFORM_SECRET=$(terraform show -json | jq -r '.values.outputs.client_secret.value') 96 | export POWER_PLATFORM_TENANT_ID=$(terraform show -json | jq -r '.values.outputs.tenant_id.value') 97 | 98 | popd 99 | 100 | echo "Bootstrap complete!" 101 | echo "" 102 | echo "You have to grant permissions to the new 'Power Platform Admin Service' service principal in the Azure portal to access the Power Platform resources." 103 | echo "" 104 | echo "You now have following options to login to the Power Platform in Terraform:" 105 | echo "1. Use the following environment variables that can be set using /bootstrap/set-local-env.sh:" 106 | echo " provider 'powerplatform' { 107 | } 108 | " 109 | 110 | echo "2. Use the client_id, client_secret and tenant_id directly: 111 | provider 'powerplatform' { 112 | client_id = var.client_id 113 | client_secret = var.client_secret 114 | tenant_id = var.tenant_id 115 | } 116 | " 117 | echo "3. Use Azure CLI to login that will be used in the provider block: 118 | For login use: az login --allow-no-subscriptions --scope api://power-platform_provider_terraform/.default 119 | 120 | provider 'powerplatform' { 121 | use_cli = true 122 | }" 123 | -------------------------------------------------------------------------------- /bootstrap/set-local-env.ps1: -------------------------------------------------------------------------------- 1 | if ($MyInvocation.MyCommand.Path -eq $PSCommandPath) { 2 | Write-Host "Rerun the script using source: . .\set-local-env.ps1" 3 | exit 1 4 | } 5 | 6 | Push-Location ./tenant-configuration 7 | 8 | $POWER_PLATFORM_CLIENT_ID = (terraform show -json | ConvertFrom-Json).values.outputs.client_id.value 9 | $POWER_PLATFORM_SECRET = (terraform show -json | ConvertFrom-Json).values.outputs.client_secret.value 10 | $POWER_PLATFORM_TENANT_ID = (terraform show -json | ConvertFrom-Json).values.outputs.tenant_id.value 11 | 12 | Pop-Location -------------------------------------------------------------------------------- /bootstrap/set-local-env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then 4 | echo "Rerun the script using source: source ./set-local-env.sh" 5 | exit 1 6 | fi 7 | 8 | pushd ./tenant-configuration 9 | 10 | export POWER_PLATFORM_CLIENT_ID=$(terraform show -json | jq -r '.values.outputs.client_id.value') 11 | export POWER_PLATFORM_SECRET=$(terraform show -json | jq -r '.values.outputs.client_secret.value') 12 | export POWER_PLATFORM_TENANT_ID=$(terraform show -json | jq -r '.values.outputs.tenant_id.value') 13 | 14 | popd -------------------------------------------------------------------------------- /bootstrap/tenant-configuration/.terraform.lock.hcl: -------------------------------------------------------------------------------- 1 | # This file is maintained automatically by "terraform init". 2 | # Manual edits may be lost in future updates. 3 | 4 | provider "registry.terraform.io/hashicorp/azuread" { 5 | version = "3.3.0" 6 | hashes = [ 7 | "h1:CmjUkS5xnsVJ6QHzooWNYg2iowb++EcGIWSCoMiUlbM=", 8 | "h1:IrIdxafjoENorektWrxmz9jx8uzH0OrGdUtt7vvHbJk=", 9 | "zh:1c3e89cf19118fc07d7b04257251fc9897e722c16e0a0df7b07fcd261f8c12e7", 10 | "zh:2cb6dcfa5a3fd04f14b1976455b73f14a15f9f45069fa5fa198f87625df5751a", 11 | "zh:3de1c29f4eb1a176a7f3039a470f26281228f9527a81ebedb86820dd9d5b0007", 12 | "zh:498ef951e158caf10365b39cd95789f5a034c3ef6fca288b37520af0cc2f81e5", 13 | "zh:7b8c8bda91a2b28b9371ac60c2c87a3967d0ca232ab868203062599fca4c832f", 14 | "zh:9b109748e2ccad027d710548491bc8cc0399b69a9b779ac59d53745a95129e3a", 15 | "zh:9d38831a614f67e09ca4bf2377a06e6dda9130a60437f4d73aa422d982b9f17f", 16 | "zh:b6fa6920334e7495118784d15500d676fda4e9c924a03fb5e333faf6646fd15f", 17 | "zh:c01171e3c1b1a6bcc9516f5fc44e4880c67a30541ae66ecf18d3aa0eb92457ad", 18 | "zh:cd3bda1e87953a5288808a10ca9383baa14b4dee623e21a43048f95179bf0323", 19 | "zh:df06f222c38fc37a074e00650e7699927b11a64b1f4170bfa7d823029662a778", 20 | "zh:df502d004558a92808450fc9a7b6f17700d918e3037d21cba73d61e56466e785", 21 | ] 22 | } 23 | 24 | provider "registry.terraform.io/hashicorp/azurerm" { 25 | version = "4.1.0" 26 | hashes = [ 27 | "h1:qDmSr5+vMVdWmfBEaIwqSLo5ZLyYk7KYoJo5flny6lk=", 28 | "zh:3f332bca3a8b7dc982e428e09c73862d1afda34c2ad7803e70d8ba7b9e2445fd", 29 | "zh:66b7e4a7a4fd06e0a5a3a22b4f76bda48e50ed3dd26c388738d9cc882b801bce", 30 | "zh:6a271175d6e079241f24129f5026e0b16f04e7a548807f115600003d615e0ec3", 31 | "zh:7a6abc7e2ae8d1041d0446bbe87156e0436639676ee1fad40321e8ee6759a454", 32 | "zh:903f6e7f03e5952347ce6ee589d58c829179f2f22220f25cf52ae4efecd7053c", 33 | "zh:a572b9834cf3b51799c82c5009705c59309d947a6ecdb7e17729868c55e7d0e0", 34 | "zh:a7fca14338f0cfb82b17ce085400c210cbc986a87086702e3a11efcb4e53d6e4", 35 | "zh:af36c7004702b0a273794914a17a77af1eb972caaad64e0068739e55c1488845", 36 | "zh:b36f308db1cdc02dee659e3e518186d7dec970d88b6149be3f6b3f8d544e4282", 37 | "zh:bedf6d13cf4bccc128d8cbb0703a3a8b547629674439ffda5e73563ab775d0f7", 38 | "zh:d1df286a2e5d4a5f6a7f4d29700a25588167ccffa31c686550ef617503df3254", 39 | "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", 40 | ] 41 | } 42 | 43 | provider "registry.terraform.io/hashicorp/null" { 44 | version = "3.2.4" 45 | hashes = [ 46 | "h1:+Ag4hSb4qQjNtAS6gj2+gsGl7v0iB/Bif6zZZU8lXsw=", 47 | "h1:hkf5w5B6q8e2A42ND2CjAvgvSN3puAosDmOJb3zCVQM=", 48 | "zh:59f6b52ab4ff35739647f9509ee6d93d7c032985d9f8c6237d1f8a59471bbbe2", 49 | "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", 50 | "zh:795c897119ff082133150121d39ff26cb5f89a730a2c8c26f3a9c1abf81a9c43", 51 | "zh:7b9c7b16f118fbc2b05a983817b8ce2f86df125857966ad356353baf4bff5c0a", 52 | "zh:85e33ab43e0e1726e5f97a874b8e24820b6565ff8076523cc2922ba671492991", 53 | "zh:9d32ac3619cfc93eb3c4f423492a8e0f79db05fec58e449dee9b2d5873d5f69f", 54 | "zh:9e15c3c9dd8e0d1e3731841d44c34571b6c97f5b95e8296a45318b94e5287a6e", 55 | "zh:b4c2ab35d1b7696c30b64bf2c0f3a62329107bd1a9121ce70683dec58af19615", 56 | "zh:c43723e8cc65bcdf5e0c92581dcbbdcbdcf18b8d2037406a5f2033b1e22de442", 57 | "zh:ceb5495d9c31bfb299d246ab333f08c7fb0d67a4f82681fbf47f2a21c3e11ab5", 58 | "zh:e171026b3659305c558d9804062762d168f50ba02b88b231d20ec99578a6233f", 59 | "zh:ed0fe2acdb61330b01841fa790be00ec6beaac91d41f311fb8254f74eb6a711f", 60 | ] 61 | } 62 | 63 | provider "registry.terraform.io/hashicorp/random" { 64 | version = "3.7.2" 65 | hashes = [ 66 | "h1:0hcNr59VEJbhZYwuDE/ysmyTS0evkfcLarlni+zATPM=", 67 | "h1:356j/3XnXEKr9nyicLUufzoF4Yr6hRy481KIxRVpK0c=", 68 | "zh:14829603a32e4bc4d05062f059e545a91e27ff033756b48afbae6b3c835f508f", 69 | "zh:1527fb07d9fea400d70e9e6eb4a2b918d5060d604749b6f1c361518e7da546dc", 70 | "zh:1e86bcd7ebec85ba336b423ba1db046aeaa3c0e5f921039b3f1a6fc2f978feab", 71 | "zh:24536dec8bde66753f4b4030b8f3ef43c196d69cccbea1c382d01b222478c7a3", 72 | "zh:29f1786486759fad9b0ce4fdfbbfece9343ad47cd50119045075e05afe49d212", 73 | "zh:4d701e978c2dd8604ba1ce962b047607701e65c078cb22e97171513e9e57491f", 74 | "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", 75 | "zh:7b8434212eef0f8c83f5a90c6d76feaf850f6502b61b53c329e85b3b281cba34", 76 | "zh:ac8a23c212258b7976e1621275e3af7099e7e4a3d4478cf8d5d2a27f3bc3e967", 77 | "zh:b516ca74431f3df4c6cf90ddcdb4042c626e026317a33c53f0b445a3d93b720d", 78 | "zh:dc76e4326aec2490c1600d6871a95e78f9050f9ce427c71707ea412a2f2f1a62", 79 | "zh:eac7b63e86c749c7d48f527671c7aee5b4e26c10be6ad7232d6860167f99dbb0", 80 | ] 81 | } 82 | -------------------------------------------------------------------------------- /bootstrap/tenant-configuration/grant-ppadmin.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [string]$client_id, 3 | [string]$action 4 | ) 5 | 6 | if (-not $client_id) { 7 | Write-Host "No client_id argument supplied" 8 | exit 1 9 | } 10 | 11 | if (-not $action) { 12 | Write-Host "No action argument supplied" 13 | exit 1 14 | } 15 | 16 | if ($action -ne "create" -and $action -ne "destroy") { 17 | Write-Host "Invalid action argument supplied. Must be either 'create' or 'destroy'" 18 | exit 1 19 | } 20 | 21 | $access_token = az account get-access-token --scope https://service.powerapps.com/.default --query accessToken --output tsv 22 | $url = "https://api.bap.microsoft.com/providers/Microsoft.BusinessAppPlatform/adminApplications/$client_id`?api-version=2024-05-01" 23 | 24 | if ($action -eq "create") { 25 | Invoke-RestMethod -Method Put -Uri $url -Headers @{Authorization = "Bearer $access_token"; Accept = "application/json"; "Content-Length" = "0"} 26 | } elseif ($action -eq "destroy") { 27 | Invoke-RestMethod -Method Delete -Uri $url -Headers @{Authorization = "Bearer $access_token"; Accept = "application/json"; "Content-Length" = "0"} 28 | } 29 | -------------------------------------------------------------------------------- /bootstrap/tenant-configuration/grant-ppadmin.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | while [[ $# -gt 0 ]] 4 | do 5 | key="$1" 6 | 7 | case $key in 8 | --client_id) 9 | client_id="$2" 10 | shift 11 | shift 12 | ;; 13 | --action) 14 | action="$2" 15 | shift 16 | shift 17 | ;; 18 | *) 19 | echo "Unknown option: $key" 20 | exit 1 21 | ;; 22 | esac 23 | done 24 | 25 | if [ -z "$client_id" ] 26 | then 27 | echo "No client_id argument supplied" 28 | exit 1 29 | fi 30 | 31 | if [ -z "$action" ] 32 | then 33 | echo "No action argument supplied" 34 | exit 1 35 | fi 36 | 37 | if [ "$action" != "create" ] && [ "$action" != "destroy" ] 38 | then 39 | echo "Invalid action argument supplied. Must be either 'create' or 'destroy'" 40 | exit 1 41 | fi 42 | 43 | access_token=$(az account get-access-token --scope https://service.powerapps.com//.default --query accessToken --output tsv) 44 | api_version="2020-10-01" 45 | url="https://api.bap.microsoft.com/providers/Microsoft.BusinessAppPlatform/adminApplications/$client_id?api-version=$api_version" 46 | 47 | if [ "$action" == "create" ] 48 | then 49 | curl -X PUT -H "Authorization: Bearer $access_token" -H "Accept: application/json" -H "Content-Length: 0" $url 50 | elif [ "$action" == "destroy" ] 51 | then 52 | curl -X DELETE -H "Authorization: Bearer $access_token" -H "Accept: application/json" -H "Content-Length: 0" $url 53 | fi 54 | -------------------------------------------------------------------------------- /bootstrap/tenant-configuration/main.tf: -------------------------------------------------------------------------------- 1 | variable "use_azurerm" { 2 | description = "Set to true to use the azurerm provider" 3 | type = bool 4 | default = false 5 | } 6 | 7 | terraform { 8 | required_providers { 9 | azuread = { 10 | source = "hashicorp/azuread" 11 | } 12 | azurerm = { 13 | source = "hashicorp/azurerm" 14 | version = "=4.0.1" 15 | } 16 | random = { 17 | source = "hashicorp/random" 18 | } 19 | null = { 20 | source = "hashicorp/null" 21 | } 22 | } 23 | } 24 | 25 | provider "azurerm" { 26 | resource_provider_registrations = "none" 27 | features {} 28 | } 29 | 30 | provider "azuread" { 31 | } 32 | 33 | # Conditional resource to control azurerm provider usage 34 | resource "null_resource" "azurerm_provider" { 35 | triggers = { 36 | use_azurerm = var.use_azurerm 37 | } 38 | } 39 | 40 | # Get a reference to the current Azure AD configuration so that we can read the tenant ID 41 | data "azuread_client_config" "current" {} 42 | 43 | # Get a reference to the Power Platform API's pre-existing service principal 44 | resource "azuread_service_principal" "power_platform_api" { 45 | client_id = "8578e004-a5c6-46e7-913e-12f58912df43" // Power Platform API 46 | use_existing = true 47 | } 48 | 49 | #get a reference to the PowerApps service principal 50 | resource "azuread_service_principal" "powerapps_service" { 51 | client_id = "475226c6-020e-4fb2-8a90-7a972cbfc1d4" 52 | use_existing = true 53 | } 54 | 55 | #get a reference to the Dynamics CRM service principal 56 | resource "azuread_service_principal" "dynamics_service" { 57 | client_id = "00000007-0000-0000-c000-000000000000" 58 | use_existing = true 59 | } 60 | 61 | # Create a new Entra ID (Azure AD) application for the Power Platform Admin Service. This is 62 | # the service account that will be used to apply terraform modules in the GitHub Actions workflow. 63 | resource "azuread_application" "ppadmin_application" { 64 | display_name = "Power Platform Admin Service" 65 | owners = [data.azuread_client_config.current.object_id] 66 | 67 | required_resource_access { 68 | resource_app_id = resource.azuread_service_principal.power_platform_api.client_id 69 | 70 | resource_access { 71 | id = resource.azuread_service_principal.power_platform_api.oauth2_permission_scope_ids["Licensing.BillingPolicies.ReadWrite"] 72 | type = "Scope" 73 | } 74 | 75 | resource_access { 76 | id = resource.azuread_service_principal.power_platform_api.oauth2_permission_scope_ids["Licensing.BillingPolicies.Read"] 77 | type = "Scope" 78 | } 79 | 80 | resource_access { 81 | id = resource.azuread_service_principal.power_platform_api.oauth2_permission_scope_ids["AppManagement.ApplicationPackages.Install"] 82 | type = "Scope" 83 | } 84 | 85 | resource_access { 86 | id = resource.azuread_service_principal.power_platform_api.oauth2_permission_scope_ids["AppManagement.ApplicationPackages.Read"] 87 | type = "Scope" 88 | } 89 | } 90 | 91 | required_resource_access { 92 | resource_app_id = resource.azuread_service_principal.powerapps_service.client_id 93 | resource_access { 94 | id = resource.azuread_service_principal.powerapps_service.oauth2_permission_scope_ids["User"] 95 | type = "Scope" 96 | } 97 | } 98 | 99 | required_resource_access { 100 | resource_app_id = resource.azuread_service_principal.dynamics_service.client_id 101 | resource_access { 102 | id = resource.azuread_service_principal.dynamics_service.oauth2_permission_scope_ids["user_impersonation"] 103 | type = "Scope" 104 | } 105 | } 106 | 107 | identifier_uris = ["api://power-platform_provider_terraform"] 108 | 109 | api { 110 | oauth2_permission_scope { 111 | admin_consent_description = "Allows connection to backend services of Power Platform Terraform Provider" 112 | admin_consent_display_name = "Power Platform Terraform Provider Access" 113 | enabled = true 114 | id = "2aedce72-ddc7-431d-920c-a321297ffdc2" 115 | type = "User" 116 | user_consent_description = "Allows connection to backend services of Power Platform Terraform Provider" 117 | user_consent_display_name = "Power Platform Terraform Provider Access" 118 | value = "user_impersonation" 119 | } 120 | } 121 | } 122 | 123 | resource "azuread_application_pre_authorized" "ppadmin_application_allow_azure_cli" { 124 | application_id = azuread_application.ppadmin_application.id 125 | authorized_client_id = "04b07795-8ddb-461a-bbee-02f9e1bf7b46" //Azure CLI first party application ID 126 | 127 | permission_ids = [ 128 | "2aedce72-ddc7-431d-920c-a321297ffdc2", 129 | ] 130 | } 131 | 132 | # Create a service principal for the Power Platform Admin Service application 133 | resource "azuread_service_principal" "ppadmin_principal" { 134 | client_id = azuread_application.ppadmin_application.client_id 135 | app_role_assignment_required = false 136 | owners = [data.azuread_client_config.current.object_id] 137 | } 138 | 139 | # Create a client secret for the Power Platform Admin Service application 140 | resource "azuread_application_password" "ppadmin_secret" { 141 | application_id = azuread_application.ppadmin_application.id 142 | } 143 | 144 | data "azurerm_storage_account" "tf_state_storage_account" { 145 | count = var.use_azurerm ? 1 : 0 146 | name = var.storage_account_name 147 | resource_group_name = var.resource_group_name 148 | depends_on = [null_resource.azurerm_provider] 149 | } 150 | 151 | # Grant the Power Platfom Admin Service Storage Blob Contributor role on the Terraform state storage account 152 | resource "azurerm_role_assignment" "ppadmin_storage_role_assignment" { 153 | count = var.use_azurerm ? 1 : 0 154 | scope = data.azurerm_storage_account.tf_state_storage_account[0].id 155 | role_definition_name = "Storage Blob Data Contributor" 156 | principal_id = azuread_service_principal.ppadmin_principal.object_id 157 | lifecycle { 158 | ignore_changes = [scope] 159 | } 160 | depends_on = [null_resource.azurerm_provider] 161 | } 162 | 163 | data "azurerm_role_definition" "contributor" { 164 | name = "Contributor" 165 | } 166 | 167 | # Grant the Power Platfom Admin Service Contributor role on the Azure Subscription 168 | resource "azurerm_role_assignment" "ppadmin_subscription_role_assignment" { 169 | count = var.use_azurerm ? 1 : 0 170 | scope = data.azurerm_subscription.current[0].id 171 | role_definition_id = data.azurerm_role_definition.contributor.id 172 | principal_id = azuread_service_principal.ppadmin_principal.object_id 173 | lifecycle { 174 | ignore_changes = [scope] 175 | } 176 | depends_on = [null_resource.azurerm_provider] 177 | } 178 | 179 | data "azurerm_subscription" "current" { 180 | count = var.use_azurerm ? 1 : 0 181 | } 182 | 183 | # Grant the Power Platform Admin Service application the permissions it needs to manage Power Platform via the BAPI APIs 184 | resource "null_resource" "ppadmin_role_assignment" { 185 | triggers = { 186 | client_id = azuread_application.ppadmin_application.client_id 187 | } 188 | provisioner "local-exec" { 189 | when = create 190 | command = "${path.module}/grant-ppadmin.ps1 ${self.triggers.client_id} create" 191 | interpreter = ["pwsh", "-Command"] 192 | } 193 | provisioner "local-exec" { 194 | when = destroy 195 | command = "${path.module}/grant-ppadmin.ps1 ${self.triggers.client_id} destroy" 196 | interpreter = ["pwsh", "-Command"] 197 | } 198 | } -------------------------------------------------------------------------------- /bootstrap/tenant-configuration/outputs.tf: -------------------------------------------------------------------------------- 1 | output "client_id" { 2 | value = azuread_application.ppadmin_application.client_id 3 | } 4 | 5 | output "client_secret" { 6 | value = azuread_application_password.ppadmin_secret.value 7 | sensitive = true 8 | } 9 | 10 | output "subscription_id" { 11 | value = var.use_azurerm ? data.azurerm_subscription.current[0].subscription_id : null 12 | } 13 | 14 | output "tenant_id" { 15 | value = var.use_azurerm ? data.azurerm_subscription.current[0].tenant_id : null 16 | } 17 | 18 | output "storage_account_name" { 19 | value = var.storage_account_name 20 | } 21 | 22 | output "resource_group_name" { 23 | value = var.resource_group_name 24 | } 25 | -------------------------------------------------------------------------------- /bootstrap/tenant-configuration/variables.tf: -------------------------------------------------------------------------------- 1 | variable "storage_account_name" { 2 | type = string 3 | default = "" 4 | description = "The name of the storage account to use for storing Terraform state." 5 | } 6 | 7 | variable "resource_group_name" { 8 | type = string 9 | default = "" 10 | description = "The name of the resource group to use for storing Terraform state." 11 | } 12 | variable "client_id" { 13 | type = string 14 | description = "The client ID of the service principal to use for authenticating to Azure." 15 | default = "00000000-0000-0000-0000-000000000000" 16 | } -------------------------------------------------------------------------------- /bootstrap/tf-backend/tf-backend.bicep: -------------------------------------------------------------------------------- 1 | targetScope = 'resourceGroup' 2 | param location string = resourceGroup().location 3 | 4 | resource sa 'Microsoft.Storage/storageAccounts@2021-06-01' = { 5 | name: 'tfstate${uniqueString(subscription().subscriptionId)}' 6 | location: location 7 | sku: { 8 | name: 'Standard_LRS' 9 | } 10 | kind: 'StorageV2' 11 | properties: { 12 | accessTier: 'Hot' 13 | encryption: { 14 | keySource: 'Microsoft.Storage' 15 | services: { 16 | blob: { 17 | enabled: true 18 | } 19 | } 20 | } 21 | } 22 | } 23 | 24 | resource bs 'Microsoft.Storage/storageAccounts/blobServices@2021-08-01' = { 25 | name: 'default' 26 | parent: sa 27 | } 28 | 29 | resource container 'Microsoft.Storage/storageAccounts/blobServices/containers@2021-06-01' = { 30 | parent: bs 31 | name: 'tfstate' 32 | properties: { 33 | publicAccess: 'None' 34 | } 35 | } 36 | 37 | resource lock 'Microsoft.Authorization/locks@2016-09-01' = { 38 | name: 'rg-delete-lock' 39 | properties: { 40 | level: 'CanNotDelete' 41 | } 42 | scope: resourceGroup() 43 | } 44 | 45 | resource saLock 'Microsoft.Authorization/locks@2016-09-01' = { 46 | name: 'sa-delete-lock' 47 | properties: { 48 | level: 'CanNotDelete' 49 | } 50 | scope: sa 51 | } 52 | 53 | output storage_account_name string = sa.name 54 | output container_name string = container.name 55 | -------------------------------------------------------------------------------- /bootstrap/tf-backend/tf-subscription.bicep: -------------------------------------------------------------------------------- 1 | param location string = 'westus' 2 | 3 | targetScope = 'subscription' 4 | 5 | resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = { 6 | name: 'terraform-state' 7 | location: location 8 | } 9 | 10 | module tfbackend 'tf-backend.bicep' = { 11 | name: 'tf-backend' 12 | scope: rg 13 | params: { 14 | location: rg.location 15 | } 16 | } 17 | 18 | resource StorageAccountDefender 'Microsoft.Security/pricings@2023-01-01' = { 19 | name: 'StorageAccounts' 20 | properties: { 21 | pricingTier: 'Standard' 22 | subPlan: 'DefenderForStorageV2' 23 | extensions: [ 24 | { 25 | name: 'OnUploadMalwareScanning' 26 | isEnabled: 'True' 27 | additionalExtensionProperties: { 28 | CapGBPerMonthPerStorageAccount: '-1' 29 | } 30 | } 31 | { 32 | name: 'SensitiveDataDiscovery' 33 | isEnabled: 'True' 34 | } 35 | ] 36 | } 37 | } 38 | 39 | output resource_group_name string = rg.name 40 | output storage_account_name string = tfbackend.outputs.storage_account_name 41 | -------------------------------------------------------------------------------- /quickstarts/101-hello-power-platform/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Hello Power Platform (101 level) 3 | 4 | This Terraform module aims to provide a fully managed infrastructure that integrates Microsoft's Power Platform and Azure services. Utilizing both power-platform and azurerm Terraform providers, this module encapsulates best practices and serves as a reference architecture for scalable, reliable, and manageable cloud infrastructure. 5 | 6 | ## Prerequisites 7 | 8 | - Entra ID Tenant 9 | - Power Platform environment 10 | - Already executed [bootsrap](../../bootstrap/README.md) script 11 | 12 | ## Example Files 13 | 14 | The example files can be found in `quickstarts/101-hello-power-platform` 15 | 16 | ## Provider Requirements 17 | 18 | The Terraform plugins or "providers" that this IaC deployment requires are: 19 | 20 | - **azuread (`hashicorp/azuread`):** (any version) 21 | 22 | - **null (`hashicorp/null`):** (any version) 23 | 24 | - **powerplatform (`microsoft/power-platform`):** `>=3.3.0` 25 | 26 | - **random (`hashicorp/random`):** (any version) 27 | 28 | ## Input Variables 29 | 30 | | Name | Description | Type | Default | Required | 31 | |------|-------------|------|---------|:--------:| 32 | | `aliases` | The aliases to create users for | list(string) | `["test1","test2"]` | false | 33 | 34 | ## Output Values 35 | 36 | | Name | Description | 37 | |------|-------------| 38 | | `dev_environment` | | 39 | | `dev_environment_access_group` | | 40 | | `test_environment` | | 41 | | `test_environment_access_group` | | 42 | | `user_credentials` | | 43 | 44 | ## Child Modules 45 | 46 | - `identity` from `./identity` 47 | 48 | - `power-platform` from `./power-platform` 49 | 50 | ## Usage 51 | 52 | Execute example with the following commands: 53 | 54 | ```bash 55 | az login --allow-no-subscriptions --scope api://power-platform_provider_terraform/.default 56 | 57 | terraform init 58 | 59 | terraform apply 60 | ``` 61 | 62 | ## Detailed Behavior 63 | 64 | ### Idenity Module 65 | 66 | The identity modules creates following resources: 67 | 68 | - given amout of users with aliases specified by `aliases` variable. 69 | - Entra security group that will be used to secure access to DEV Power Platform environment. 70 | - Entra security group that will be used to secure access to TEST Power Platform environment. 71 | 72 | ### Power Platform Module 73 | 74 | The Powe Platform modules creates following resources: 75 | 76 | - DEV Power Platform environment 77 | - TEST Power Platform environment 78 | - enables mananged environment for DEV and TEST environments 79 | - secures DEV and TEST environments with Entra security groups created in the identity module 80 | 81 | ## Limitations and Considerations 82 | 83 | - This module is provided as a sample only and is not intended for production use without further customization. 84 | -------------------------------------------------------------------------------- /quickstarts/101-hello-power-platform/README.md.tmpl: -------------------------------------------------------------------------------- 1 | # Hello Power Platform (101 level) 2 | 3 | This Terraform module aims to provide a fully managed infrastructure that integrates Microsoft's Power Platform and Azure services. Utilizing both power-platform and azurerm Terraform providers, this module encapsulates best practices and serves as a reference architecture for scalable, reliable, and manageable cloud infrastructure. 4 | 5 | ## Prerequisites 6 | 7 | - Entra ID Tenant 8 | - Power Platform environment 9 | - Already executed [bootsrap](../../bootstrap/README.md) script 10 | 11 | {{ .ModuleDetails }} 12 | 13 | ## Usage 14 | 15 | Execute example with the following commands: 16 | 17 | ```bash 18 | az login --allow-no-subscriptions --scope api://power-platform_provider_terraform/.default 19 | 20 | terraform init 21 | 22 | terraform apply 23 | ``` 24 | 25 | ## Detailed Behavior 26 | 27 | ### Idenity Module 28 | 29 | The identity modules creates following resources: 30 | 31 | - given amout of users with aliases specified by `aliases` variable. 32 | - Entra security group that will be used to secure access to DEV Power Platform environment. 33 | - Entra security group that will be used to secure access to TEST Power Platform environment. 34 | 35 | ### Power Platform Module 36 | 37 | The Powe Platform modules creates following resources: 38 | 39 | - DEV Power Platform environment 40 | - TEST Power Platform environment 41 | - enables mananged environment for DEV and TEST environments 42 | - secures DEV and TEST environments with Entra security groups created in the identity module 43 | 44 | ## Limitations and Considerations 45 | 46 | - This module is provided as a sample only and is not intended for production use without further customization. 47 | -------------------------------------------------------------------------------- /quickstarts/101-hello-power-platform/identity/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | azuread = { 4 | source = "hashicorp/azuread" 5 | } 6 | random = { 7 | source = "hashicorp/random" 8 | } 9 | } 10 | } 11 | 12 | data "azuread_domains" "aad_domains" { 13 | only_initial = true 14 | } 15 | 16 | locals { 17 | domain_name = data.azuread_domains.aad_domains.domains[0].domain_name 18 | } 19 | 20 | resource "random_password" "passwords" { 21 | count = length(var.aliases) 22 | length = 16 23 | special = true 24 | override_special = "_%@" 25 | } 26 | 27 | resource "azuread_user" "example" { 28 | count = length(var.aliases) 29 | user_principal_name = "${var.aliases[count.index]}@${local.domain_name}" 30 | display_name = var.aliases[count.index] 31 | mail_nickname = var.aliases[count.index] 32 | password = random_password.passwords[count.index].result 33 | } 34 | 35 | resource "azuread_group" "dev_access" { 36 | display_name = "Dataverse Dev Access" 37 | description = "Dataverse Dev Access Group for Power Platform" 38 | mail_enabled = false 39 | security_enabled = true 40 | } 41 | 42 | resource "azuread_group" "test_access" { 43 | display_name = "Dataverse Test Access" 44 | description = "Dataverse Test Access Group for Power Platform" 45 | mail_enabled = false 46 | security_enabled = true 47 | members = azuread_user.example[*].object_id 48 | } 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /quickstarts/101-hello-power-platform/identity/output.tf: -------------------------------------------------------------------------------- 1 | output "user_credentials" { 2 | value = { 3 | for idx, user in azuread_user.example : 4 | user.user_principal_name => { 5 | user_principal_name = user.user_principal_name 6 | } 7 | } 8 | } 9 | 10 | output "dev_environment_access_group" { 11 | value = { 12 | id = azuread_group.dev_access.id 13 | name = azuread_group.dev_access.display_name 14 | } 15 | } 16 | 17 | output "test_environment_access_group" { 18 | value = { 19 | id = azuread_group.test_access.id 20 | name = azuread_group.test_access.display_name 21 | } 22 | } -------------------------------------------------------------------------------- /quickstarts/101-hello-power-platform/identity/variables.tf: -------------------------------------------------------------------------------- 1 | variable "aliases" { 2 | description = "The aliases to create users for" 3 | type = list(string) 4 | } -------------------------------------------------------------------------------- /quickstarts/101-hello-power-platform/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | azuread = { 4 | source = "hashicorp/azuread" 5 | } 6 | random = { 7 | source = "hashicorp/random" 8 | } 9 | null = { 10 | source = "hashicorp/null" 11 | } 12 | powerplatform = { 13 | source = "microsoft/power-platform" 14 | version = ">=3.3.0" 15 | } 16 | } 17 | } 18 | 19 | provider "powerplatform" { 20 | use_cli = true 21 | } 22 | 23 | module "identity" { 24 | source = "./identity" 25 | aliases = var.aliases 26 | } 27 | 28 | module "power-platform" { 29 | source = "./power-platform" 30 | dev_environment_access_group_id = module.identity.dev_environment_access_group.id 31 | test_environment_access_group_id = module.identity.test_environment_access_group.id 32 | } -------------------------------------------------------------------------------- /quickstarts/101-hello-power-platform/output.tf: -------------------------------------------------------------------------------- 1 | output "user_credentials" { 2 | value = module.identity.user_credentials 3 | } 4 | 5 | output "dev_environment_access_group" { 6 | value = module.identity.dev_environment_access_group 7 | } 8 | 9 | output "test_environment_access_group" { 10 | value = module.identity.test_environment_access_group 11 | } 12 | 13 | output "dev_environment" { 14 | value = { 15 | id = module.power-platform.dev_environment.id 16 | url = module.power-platform.dev_environment.url 17 | version = module.power-platform.dev_environment.version 18 | } 19 | } 20 | 21 | output "test_environment" { 22 | value = { 23 | id = module.power-platform.test_environment.id 24 | url = module.power-platform.test_environment.url 25 | version = module.power-platform.test_environment.version 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /quickstarts/101-hello-power-platform/power-platform/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | powerplatform = { 4 | source = "microsoft/power-platform" 5 | version = ">=3.3.0" 6 | } 7 | } 8 | } 9 | 10 | resource "powerplatform_environment" "dev" { 11 | location = "unitedstates" 12 | display_name = "terraformdev" 13 | environment_type = "Sandbox" 14 | dataverse = { 15 | language_code = 1033 16 | currency_code = "USD" 17 | security_group_id = var.dev_environment_access_group_id 18 | } 19 | } 20 | 21 | resource "powerplatform_managed_environment" "dev_managed" { 22 | environment_id = powerplatform_environment.dev.id 23 | is_usage_insights_disabled = true 24 | is_group_sharing_disabled = true 25 | limit_sharing_mode = "ExcludeSharingToSecurityGroups" 26 | max_limit_user_sharing = 10 27 | solution_checker_mode = "None" 28 | suppress_validation_emails = true 29 | maker_onboarding_markdown = "Welcome to the ${powerplatform_environment.dev.display_name} environment!" 30 | maker_onboarding_url = "https://www.contoso.com/onboarding" 31 | } 32 | 33 | resource "powerplatform_environment" "test" { 34 | location = "unitedstates" 35 | display_name = "terraformtest" 36 | environment_type = "Sandbox" 37 | dataverse = { 38 | language_code = 1033 39 | currency_code = "USD" 40 | security_group_id = var.test_environment_access_group_id 41 | } 42 | } 43 | 44 | resource "powerplatform_managed_environment" "test_managed" { 45 | environment_id = powerplatform_environment.test.id 46 | is_usage_insights_disabled = false 47 | is_group_sharing_disabled = false 48 | limit_sharing_mode = "ExcludeSharingToSecurityGroups" 49 | max_limit_user_sharing = 100 50 | solution_checker_mode = "None" 51 | suppress_validation_emails = false 52 | maker_onboarding_markdown = "Welcome to the ${powerplatform_environment.test.display_name} environment!" 53 | maker_onboarding_url = "https://www.contoso.com/onboarding" 54 | } 55 | -------------------------------------------------------------------------------- /quickstarts/101-hello-power-platform/power-platform/output.tf: -------------------------------------------------------------------------------- 1 | output "dev_environment" { 2 | value = powerplatform_environment.dev 3 | } 4 | 5 | output "test_environment" { 6 | value = powerplatform_environment.test 7 | } 8 | -------------------------------------------------------------------------------- /quickstarts/101-hello-power-platform/power-platform/variables.tf: -------------------------------------------------------------------------------- 1 | variable "dev_environment_access_group_id" { 2 | description = "The id of the dev environment Entra security access group" 3 | type = string 4 | } 5 | 6 | variable "test_environment_access_group_id" { 7 | description = "The id of the test environment Entra security access group" 8 | type = string 9 | } -------------------------------------------------------------------------------- /quickstarts/101-hello-power-platform/variables.tf: -------------------------------------------------------------------------------- 1 | variable "aliases" { 2 | description = "The aliases to create users for" 3 | type = list(string) 4 | default = [ "test1", "test2" ] 5 | } -------------------------------------------------------------------------------- /quickstarts/102-github-pipeline/README.md: -------------------------------------------------------------------------------- 1 | 2 | # GitHub Pipeline Example (102 level) 3 | 4 | This example demonstrates how to create a pipeline that will deploy a Power Platform environment using Terraform. 5 | 6 | ## Prerequisites 7 | 8 | - Entra ID Tenant 9 | - Azure subscription where the Terraform state will be stored 10 | - Power Platform environment 11 | - Already executed [bootstrap](../../bootstrap/README.md) script 12 | - Configured federation between the GitHub repository that you use for this pipeline and the Entra ID tenant: 13 | 14 | ## Example Files 15 | 16 | The example files can be found in `quickstarts/102-github-pipeline` 17 | 18 | ## Provider Requirements 19 | 20 | The Terraform plugins or "providers" that this IaC deployment requires are: 21 | 22 | - **powerplatform (`microsoft/power-platform`):** `>=3.3.0` 23 | 24 | ## Resources 25 | 26 | - `powerplatform_environment.dev` from `powerplatform` 27 | 28 | ## Usage 29 | 30 | You can fork or download this repository and use it as a starting point for your own pipeline. Copy the [tf-102-example-pipeline.yml](./tf-102-example-pipeline.yml) to the `.github/workflows` directory in your repository. 31 | 32 | You will have to set the following [secrets in your repository](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables#creating-configuration-variables-for-a-repository): 33 | 34 | - PPADMIN_CLIENT_ID = `` 35 | - PPADMIN_TENANT_ID = `` 36 | - TF_STATE_STORAGE_ACCOUNT_NAME = `` 37 | - TF_STATE_RESOURCE_GROUP_NAME = `` 38 | 39 | To run the pipeline, you will have to create a new branch based on your main branch, push it, and create a pull request. The pipeline will run the `Terraform Plan` step on every push to the repository. 40 | The pipeline will authenticate using OpenID Connect and will require setting federation between the GitHub repository and the Entra ID tenant. With federation configured, no additional credentials are required to execute pipeline steps against Azure or Power Platform. 41 | 42 | ![pipeline1](images/pipeline-1.png) 43 | 44 | The Terraform Plan output will also be added to your pull request as a comment: 45 | 46 | ![plan_output](images/plan-output.png) 47 | 48 | ## Detailed Behavior 49 | 50 | The pipeline example was created to demonstrate how to deploy Power Platform environment using Terraform. The pipeline is created from two steps 51 | 52 | - `Terraform Plan`: is responsible for creating a plan of the changes that will be applied to the infrastructure. The plan is stored as an artifact and can be reviewed before applying the changes. This step will run on every push to the repository. 53 | 54 | - `Terraform Apply`: is responsible for applying the changes to the infrastructure. The changes are applied only if the plan was reviewed and approved. This step will run on every push `main` branch. 55 | 56 | ![pipeline2](images/pipeline--2.png) 57 | 58 | ## Limitations and Considerations 59 | 60 | - This module is provided as a sample only and is not intended for production use without further customization. 61 | -------------------------------------------------------------------------------- /quickstarts/102-github-pipeline/README.md.tmpl: -------------------------------------------------------------------------------- 1 | # GitHub Pipeline Example (102 level) 2 | 3 | This example demonstrates how to create a pipeline that will deploy a Power Platform environment using Terraform. 4 | 5 | ## Prerequisites 6 | 7 | - Entra ID Tenant 8 | - Azure subscription where the Terraform state will be stored 9 | - Power Platform environment 10 | - Already executed [bootstrap](../../bootstrap/README.md) script 11 | - Configured federation between the GitHub repository that you use for this pipeline and the Entra ID tenant: 12 | 13 | {{ .ModuleDetails }} 14 | 15 | ## Usage 16 | 17 | You can fork or download this repository and use it as a starting point for your own pipeline. Copy the [tf-102-example-pipeline.yml](./tf-102-example-pipeline.yml) to the `.github/workflows` directory in your repository. 18 | 19 | You will have to set the following [secrets in your repository](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables#creating-configuration-variables-for-a-repository): 20 | 21 | - PPADMIN_CLIENT_ID = `` 22 | - PPADMIN_TENANT_ID = `` 23 | - TF_STATE_STORAGE_ACCOUNT_NAME = `` 24 | - TF_STATE_RESOURCE_GROUP_NAME = `` 25 | 26 | To run the pipeline, you will have to create a new branch based on your main branch, push it, and create a pull request. The pipeline will run the `Terraform Plan` step on every push to the repository. 27 | The pipeline will authenticate using OpenID Connect and will require setting federation between the GitHub repository and the Entra ID tenant. With federation configured, no additional credentials are required to execute pipeline steps against Azure or Power Platform. 28 | 29 | ![pipeline1](images/pipeline-1.png) 30 | 31 | The Terraform Plan output will also be added to your pull request as a comment: 32 | 33 | ![plan_output](images/plan-output.png) 34 | 35 | ## Detailed Behavior 36 | 37 | The pipeline example was created to demonstrate how to deploy Power Platform environment using Terraform. The pipeline is created from two steps 38 | 39 | - `Terraform Plan`: is responsible for creating a plan of the changes that will be applied to the infrastructure. The plan is stored as an artifact and can be reviewed before applying the changes. This step will run on every push to the repository. 40 | 41 | - `Terraform Apply`: is responsible for applying the changes to the infrastructure. The changes are applied only if the plan was reviewed and approved. This step will run on every push `main` branch. 42 | 43 | ![pipeline2](images/pipeline--2.png) 44 | 45 | ## Limitations and Considerations 46 | 47 | - This module is provided as a sample only and is not intended for production use without further customization. 48 | -------------------------------------------------------------------------------- /quickstarts/102-github-pipeline/images/pipeline--2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/power-platform-terraform-quickstarts/7c0273dda635b694aff8ae35eaa1a966521da4f7/quickstarts/102-github-pipeline/images/pipeline--2.png -------------------------------------------------------------------------------- /quickstarts/102-github-pipeline/images/pipeline-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/power-platform-terraform-quickstarts/7c0273dda635b694aff8ae35eaa1a966521da4f7/quickstarts/102-github-pipeline/images/pipeline-1.png -------------------------------------------------------------------------------- /quickstarts/102-github-pipeline/images/plan-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/power-platform-terraform-quickstarts/7c0273dda635b694aff8ae35eaa1a966521da4f7/quickstarts/102-github-pipeline/images/plan-output.png -------------------------------------------------------------------------------- /quickstarts/102-github-pipeline/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | powerplatform = { 4 | source = "microsoft/power-platform" 5 | version = ">=3.3.0" 6 | } 7 | } 8 | 9 | backend "azurerm" { 10 | container_name = "tfstate" 11 | key = "pipeline-example.terraform.tfstate" 12 | use_oidc = true 13 | } 14 | } 15 | 16 | provider "powerplatform" { 17 | use_oidc = true 18 | } 19 | 20 | resource "powerplatform_environment" "dev" { 21 | location = "unitedstates" 22 | display_name = "pipeline-example" 23 | environment_type = "Sandbox" 24 | dataverse = { 25 | language_code = 1033 26 | currency_code = "USD" 27 | security_group_id = "00000000-0000-0000-0000-000000000000" 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /quickstarts/102-github-pipeline/tf-102-example-pipeline.yml: -------------------------------------------------------------------------------- 1 | name: '102-Github-Pipilene-Example - Plan/Apply' 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | permissions: 12 | id-token: write 13 | contents: read 14 | pull-requests: write 15 | 16 | #These environment variables are used by the terraform azure provider to setup OIDD authenticate. 17 | env: 18 | ARM_USE_OIDC: true 19 | ARM_CLIENT_ID: "${{ secrets.PPADMIN_CLIENT_ID }}" 20 | ARM_TENANT_ID: "${{ secrets.PPADMIN_TENANT_ID }}" 21 | POWER_PLATFORM_USE_OIDC: true 22 | POWER_PLATFORM_CLIENT_ID: ${{ secrets.PPADMIN_CLIENT_ID }} 23 | POWER_PLATFORM_TENANT_ID: ${{ secrets.PPADMIN_TENANT_ID }} 24 | TF_STATE_STORAGE_ACCOUNT_NAME: ${{ secrets.TF_STATE_STORAGE_ACCOUNT_NAME }} 25 | TF_STATE_RESOURCE_GROUP_NAME: ${{ secrets.TF_STATE_RESOURCE_GROUP_NAME }} 26 | TARGET_DIR: ${{ github.workspace }}/quickstarts/102-github-pipeline 27 | 28 | jobs: 29 | terraform-plan: 30 | name: 'Terraform Plan' 31 | runs-on: ubuntu-latest 32 | env: 33 | #this is needed since we are running terraform with read-only permissions 34 | ARM_SKIP_PROVIDER_REGISTRATION: true 35 | outputs: 36 | tfplanExitCode: ${{ steps.tf-plan.outputs.exitcode }} 37 | 38 | steps: 39 | - name: Debug 40 | uses: raven-actions/debug@v1 41 | with: 42 | vars-context: ${{ toJson(vars) }} # optional 43 | secrets-context: ${{ toJson(secrets) }} # optional 44 | needs-context: ${{ toJson(needs) }} # optional 45 | inputs-context: ${{ toJson(inputs) }} # optional 46 | 47 | # Checkout the repository to the GitHub Actions runner 48 | - name: Checkout 49 | uses: actions/checkout@v3 50 | 51 | # Install the latest version of the Terraform CLI 52 | - name: Setup Terraform 53 | uses: hashicorp/setup-terraform@v2 54 | with: 55 | terraform_wrapper: false 56 | 57 | # Initialize a new or existing Terraform working directory by creating initial files, loading any remote state, downloading modules, etc. 58 | - name: Terraform Init 59 | run: terraform -chdir=$TARGET_DIR init -backend-config="storage_account_name=$TF_STATE_STORAGE_ACCOUNT_NAME" -backend-config="resource_group_name=$TF_STATE_RESOURCE_GROUP_NAME" 60 | 61 | # Generates an execution plan for Terraform 62 | # An exit code of 0 indicated no changes, 1 a terraform failure, 2 there are pending changes. 63 | - name: Terraform Plan 64 | id: tf-plan 65 | run: | 66 | export exitcode=0 67 | terraform -chdir=$TARGET_DIR plan -detailed-exitcode -no-color -out tfplan || export exitcode=$? 68 | 69 | echo "exitcode=$exitcode" >> $GITHUB_OUTPUT 70 | 71 | if [ $exitcode -eq 1 ]; then 72 | echo Terraform Plan Failed! 73 | exit 1 74 | else 75 | exit 0 76 | fi 77 | 78 | # Save plan to artifacts 79 | - name: Publish Terraform Plan 80 | uses: actions/upload-artifact@v3 81 | with: 82 | name: tfplan 83 | path: ${{ env.TARGET_DIR }}/tfplan 84 | 85 | # Create string output of Terraform Plan 86 | - name: Create String Output 87 | id: tf-plan-string 88 | run: | 89 | TERRAFORM_PLAN=$(terraform -chdir=$TARGET_DIR show -no-color tfplan) 90 | 91 | delimiter="$(openssl rand -hex 8)" 92 | echo "summary<<${delimiter}" >> $GITHUB_OUTPUT 93 | echo "## Terraform Plan Output" >> $GITHUB_OUTPUT 94 | echo "
Click to expand" >> $GITHUB_OUTPUT 95 | echo "" >> $GITHUB_OUTPUT 96 | echo '```terraform' >> $GITHUB_OUTPUT 97 | echo "$TERRAFORM_PLAN" >> $GITHUB_OUTPUT 98 | echo '```' >> $GITHUB_OUTPUT 99 | echo "
" >> $GITHUB_OUTPUT 100 | echo "${delimiter}" >> $GITHUB_OUTPUT 101 | 102 | # Publish Terraform Plan as task summary 103 | - name: Publish Terraform Plan to Task Summary 104 | env: 105 | SUMMARY: ${{ steps.tf-plan-string.outputs.summary }} 106 | run: | 107 | echo "$SUMMARY" >> $GITHUB_STEP_SUMMARY 108 | 109 | # If this is a PR post the changes 110 | - name: Push Terraform Output to PR 111 | if: github.ref != 'refs/heads/main' 112 | uses: actions/github-script@v6 113 | env: 114 | SUMMARY: "${{ steps.tf-plan-string.outputs.summary }}" 115 | with: 116 | github-token: ${{ github.token }} 117 | script: | 118 | const body = `${process.env.SUMMARY}`; 119 | github.rest.issues.createComment({ 120 | issue_number: context.issue.number, 121 | owner: context.repo.owner, 122 | repo: context.repo.repo, 123 | body: body 124 | }) 125 | 126 | terraform-apply: 127 | name: 'Terraform Apply' 128 | if: github.ref == 'refs/heads/main' && needs.terraform-plan.outputs.tfplanExitCode == 2 129 | runs-on: ubuntu-latest 130 | needs: [terraform-plan] 131 | env: 132 | #this is needed since we are running terraform with read-only permissions 133 | ARM_SKIP_PROVIDER_REGISTRATION: true 134 | 135 | steps: 136 | # Checkout the repository to the GitHub Actions runner 137 | - name: Checkout 138 | uses: actions/checkout@v3 139 | 140 | # Install the latest version of the Terraform CLI 141 | - name: Setup Terraform 142 | uses: hashicorp/setup-terraform@v2 143 | with: 144 | terraform_wrapper: false 145 | 146 | # Initialize a new or existing Terraform working directory by creating initial files, loading any remote state, downloading modules, etc. 147 | - name: Terraform Init 148 | run: terraform -chdir=$TARGET_DIR init -backend-config="storage_account_name=$TF_STATE_STORAGE_ACCOUNT_NAME" -backend-config="resource_group_name=$TF_STATE_RESOURCE_GROUP_NAME" 149 | 150 | # Download saved plan from artifacts 151 | - name: Download Terraform Plan 152 | uses: actions/download-artifact@v3 153 | with: 154 | name: tfplan 155 | path: ${{ env.TARGET_DIR }} 156 | 157 | # Terraform Apply 158 | - name: Terraform Apply 159 | run: terraform -chdir=$TARGET_DIR apply -auto-approve tfplan 160 | 161 | -------------------------------------------------------------------------------- /quickstarts/103-demo-tenant/.gitignore: -------------------------------------------------------------------------------- 1 | trace.log -------------------------------------------------------------------------------- /quickstarts/103-demo-tenant/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Demo Tenant Example (103 level) 3 | 4 | Provides a basic sample how to setup a demo tenant with users that each have a developer environment. 5 | 6 | Licenses required for Power Platform development are managed via a Maker security group to allow Power Apps and Power Automate development 7 | 8 | ## Prerequisites 9 | 10 | - Entra ID Tenant 11 | - Already executed [bootstrap](../../bootstrap/README.md) script 12 | 13 | ## Example Files 14 | 15 | The example files can be found in `quickstarts/103-demo-tenant` 16 | 17 | ## Provider Requirements 18 | 19 | The Terraform plugins or "providers" that this IaC deployment requires are: 20 | 21 | - **azuread:** (any version) 22 | 23 | - **azurerm:** (any version) 24 | 25 | - **external:** (any version) 26 | 27 | - **local:** (any version) 28 | 29 | ## Input Variables 30 | 31 | | Name | Description | Type | Default | Required | 32 | |------|-------------|------|---------|:--------:| 33 | | `group_description` | The description of the Azure AD group | string | `null` | true | 34 | | `group_name` | The display name of the Azure AD group | string | `null` | true | 35 | 36 | ## Data Sources 37 | 38 | - `data.external.available_license_skus` from `external` 39 | 40 | - `data.external.domain_name` from `external` 41 | 42 | - `data.external.tenant_id` from `external` 43 | 44 | - `data.local_file.users` from `local` 45 | 46 | ## Child Modules 47 | 48 | - `environments` from `./modules/environments` 49 | 50 | - `groups` from `./modules/groups` 51 | 52 | - `licenses` from `./modules/licenses` 53 | 54 | - `users` from `./modules/users` 55 | 56 | ## Usage 57 | 58 | Login to your Azure tenant 59 | 60 | ``` 61 | az login --use-device-code --allow-no-subscriptions 62 | ``` 63 | 64 | Login to the Power Platform CLI replacing **01234567-1111-2222-3333-44445555666** with your tenant 65 | 66 | ``` 67 | pac auth clear 68 | pac auth create --tenant 01234567-1111-2222-3333-44445555666 69 | ``` 70 | 71 | Apply the script using sample values 72 | 73 | ``` 74 | terraform apply -var-file=sample.tfvars.txt 75 | ``` 76 | 77 | ## Detailed Behavior 78 | 79 | ### Environments Module 80 | 81 | The environemnts modules creates following resources: 82 | 83 | - Power Platform Developer environments in the format "{firstName} {latName} Dev" 84 | 85 | ### Groups Module 86 | 87 | The environemnts modules creates following resources: 88 | 89 | - Entra security enabled group using provided variable 90 | - Members of group from variable 91 | 92 | ### Licenses 93 | 94 | The licenses modules creates following resources: 95 | 96 | - Assignment of licenses to an Entra Security group 97 | 98 | ### Users 99 | 100 | The users modules creates following resources: 101 | 102 | - Entra users with a random password 103 | 104 | ## Limitations and Considerations 105 | 106 | - This module is provided as a sample only and is not intended for production use without further customization. -------------------------------------------------------------------------------- /quickstarts/103-demo-tenant/README.md.tmpl: -------------------------------------------------------------------------------- 1 | # Demo Tenant Example (103 level) 2 | 3 | Provides a basic sample how to setup a demo tenant with users that each have a developer environment. 4 | 5 | Licenses required for Power Platform development are managed via a Maker security group to allow Power Apps and Power Automate development 6 | 7 | ## Prerequisites 8 | 9 | - Entra ID Tenant 10 | - Already executed [bootstrap](../../bootstrap/README.md) script 11 | 12 | {{ .ModuleDetails }} 13 | 14 | ## Usage 15 | 16 | Login to your Azure tenant 17 | 18 | ``` 19 | az login --use-device-code --allow-no-subscriptions 20 | ``` 21 | 22 | Login to the Power Platform CLI replacing **01234567-1111-2222-3333-44445555666** with your tenant 23 | 24 | ``` 25 | pac auth clear 26 | pac auth create --tenant 01234567-1111-2222-3333-44445555666 27 | ``` 28 | 29 | Apply the script using sample values 30 | 31 | ``` 32 | terraform apply -var-file=sample.tfvars.txt 33 | ``` 34 | 35 | ## Detailed Behavior 36 | 37 | ### Environments Module 38 | 39 | The environemnts modules creates following resources: 40 | 41 | - Power Platform Developer environments in the format "{firstName} {latName} Dev" 42 | 43 | ### Groups Module 44 | 45 | The environemnts modules creates following resources: 46 | 47 | - Entra security enabled group using provided variable 48 | - Members of group from variable 49 | 50 | ### Licenses 51 | 52 | The licenses modules creates following resources: 53 | 54 | - Assignment of licenses to an Entra Security group 55 | 56 | ### Users 57 | 58 | The users modules creates following resources: 59 | 60 | - Entra users with a random password 61 | 62 | ## Limitations and Considerations 63 | 64 | - This module is provided as a sample only and is not intended for production use without further customization. -------------------------------------------------------------------------------- /quickstarts/103-demo-tenant/assign_license.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [string]$groupId, 3 | [string]$licenseSkus 4 | ) 5 | 6 | # Function to get the access token from the logged-in Azure CLI context 7 | function Get-AccessToken { 8 | $response = az account get-access-token --resource https://graph.microsoft.com 9 | $token = $response | ConvertFrom-Json 10 | return $token.accessToken 11 | } 12 | 13 | # Get the access token 14 | $accessToken = Get-AccessToken 15 | 16 | # Get the assigned licenses for the group 17 | $group = Invoke-RestMethod -Method Get -Uri "https://graph.microsoft.com/v1.0/groups/$groupId" -Headers @{Authorization = "Bearer $accessToken"} 18 | $assignedLicenses = $group.assignedLicenses 19 | 20 | foreach ($sku in ($licenseSkus -split ',')) { 21 | # Check if the SKU is already assigned to the group 22 | $isAssigned = $assignedLicenses | Where-Object { $_.skuId -eq $sku } 23 | 24 | if ($null -eq $isAssigned) { 25 | Write-Output "Adding SKU: $sku to the list of licenses to assign" 26 | $body = @{ 27 | addLicenses = @(@{skuId = $sku; disabledPlans = @()}) 28 | removeLicenses = @() 29 | } 30 | $jsonBody = $body | ConvertTo-Json -Depth 3 31 | Write-Output "JSON Body: $jsonBody" # Debugging output 32 | Invoke-RestMethod -Method Post -Uri "https://graph.microsoft.com/v1.0/groups/$groupId/assignLicense" -Headers @{Authorization = "Bearer $accessToken"; "Content-Type" = "application/json"} -Body $jsonBody 33 | } else { 34 | Write-Output "SKU: $sku is already assigned to Group: $groupId" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /quickstarts/103-demo-tenant/check_and_create_env.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [string]$envName, 3 | [string]$firstName, 4 | [string]$lastName, 5 | [string]$domain 6 | ) 7 | 8 | # Authenticate with Azure and get an access token for the Power Platform API 9 | $accessToken = az account get-access-token --resource https://service.powerapps.com --query accessToken --output tsv 10 | 11 | # Query the Power Platform API for the list of environments 12 | $headers = @{ 13 | "Authorization" = "Bearer $accessToken" 14 | } 15 | $environments = Invoke-RestMethod -Uri "https://api.bap.microsoft.com/providers/Microsoft.BusinessAppPlatform/scopes/admin/environments?api-version=2024-05-01" -Headers $headers 16 | 17 | # Check if the environment already exists 18 | $envExists = $environments.value | Where-Object { $_.properties.displayName -eq $envName } 19 | 20 | # If the environment does not exist, create it 21 | if (-not $envExists) { 22 | # Create environment on behalf of the user 23 | pac admin create --name "$envName" --type "Developer" --user "$firstName.$lastName@$domain" 24 | 25 | # Query the Power Platform API again to get the updated list of environments 26 | $environments = Invoke-RestMethod -Uri "https://api.bap.microsoft.com/providers/Microsoft.BusinessAppPlatform/scopes/admin/environments?api-version=2024-05-01" -Headers $headers 27 | $envExists = $environments.value | Where-Object { $_.properties.displayName -eq $envName } 28 | } 29 | 30 | # Extract the environment ID and instance URL from the output 31 | $envId = $envExists.name 32 | $instanceUrl = $envExists.properties.linkedEnvironmentMetadata.instanceUrl 33 | 34 | # Output the environment ID and instance URL 35 | Write-Output "environment_id=$envId`ninstance_url=$instanceUrl" 36 | -------------------------------------------------------------------------------- /quickstarts/103-demo-tenant/get_graph_access_token.ps1: -------------------------------------------------------------------------------- 1 | # Get the access token for Microsoft Graph API using Azure CLI 2 | $accessToken = az account get-access-token --resource https://graph.microsoft.com --query accessToken --output tsv 3 | 4 | # Output the access token as a JSON object 5 | $output = @{ 6 | accessToken = $accessToken 7 | } 8 | 9 | $output | ConvertTo-Json -------------------------------------------------------------------------------- /quickstarts/103-demo-tenant/get_license_skus.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = "Stop" 2 | $logFile = "trace.log" 3 | 4 | try { 5 | # Redirect output to trace.log 6 | "Getting access token..." | Out-File -FilePath $logFile -Append 7 | $token = az account get-access-token --resource=https://graph.microsoft.com --query accessToken --output tsv 8 | "Access token obtained" | Out-File -FilePath $logFile -Append 9 | 10 | # Query the subscribed SKUs 11 | $headers = @{ 12 | Authorization = "Bearer $token" 13 | } 14 | "Querying subscribed SKUs..." | Out-File -FilePath $logFile -Append 15 | $skus = Invoke-RestMethod -Method Get -Uri "https://graph.microsoft.com/v1.0/subscribedSkus" -Headers $headers 16 | "Subscribed SKUs: $($skus | ConvertTo-Json -Compress -Depth 100)" | Out-File -FilePath $logFile -Append 17 | 18 | # Filter the SKUs 19 | $filteredSkus = $skus.value | Where-Object { 20 | $_.skuPartNumber -eq "POWERAPPS_DEV" -or # Power Apps for Developer 21 | $_.skuPartNumber -eq "FLOW_FREE" -or # Power Automate Trial 22 | $_.skuPartNumber -eq "POWERAUTOMATE_ATTENDED_RPA" # Power Automate Premium 23 | $_.skuPartNumber -eq "SPB" # Mirosoft 365 Business Premium 24 | } 25 | "Filtered SKUs: $($filteredSkus | ConvertTo-Json -Compress -Depth 100)" | Out-File -FilePath $logFile -Append 26 | 27 | # Return only the skuId of the matches as a comma-delimited string 28 | $skuIds = $filteredSkus | Select-Object -ExpandProperty skuId 29 | $result = @{ result = ($skuIds -join ",") } 30 | 31 | $result | ConvertTo-Json -Compress -Depth 100 | Out-File -FilePath $logFile -Append 32 | $result | ConvertTo-Json -Compress -Depth 100 33 | } catch { 34 | $errorResult = @{ error = $_.Exception.Message } 35 | $errorResult | ConvertTo-Json -Compress -Depth 100 36 | exit 1 37 | } 38 | -------------------------------------------------------------------------------- /quickstarts/103-demo-tenant/main.tf: -------------------------------------------------------------------------------- 1 | # Configure the Azure Resource Manager provider 2 | provider "azurerm" { 3 | features {} 4 | } 5 | 6 | # Configure the Azure Active Directory provider 7 | provider "azuread" { 8 | tenant_id = data.external.tenant_id.result.result 9 | } 10 | 11 | # Configure the local provider 12 | provider "local" {} 13 | 14 | # Retrieve the domain name of the current user 15 | data "external" "domain_name" { 16 | program = ["pwsh", "-Command", < local.env_data[i].environment_id } 29 | } 30 | 31 | output "instance_urls" { 32 | value = { for i, user in var.users : "${user.firstName} ${user.lastName} Dev" => local.env_data[i].instance_url } 33 | } 34 | -------------------------------------------------------------------------------- /quickstarts/103-demo-tenant/modules/environments/outputs.tf: -------------------------------------------------------------------------------- 1 | output "user_environment_ids" { 2 | value = { for i, user in var.users : "${user.firstName} ${user.lastName} Dev" => local.env_data[i].environment_id } 3 | } 4 | 5 | output "user_instance_urls" { 6 | value = { for i, user in var.users : "${user.firstName} ${user.lastName} Dev" => local.env_data[i].instance_url } 7 | } 8 | -------------------------------------------------------------------------------- /quickstarts/103-demo-tenant/modules/environments/variables.tf: -------------------------------------------------------------------------------- 1 | variable "users" { 2 | description = "List of user objects with first and last names" 3 | type = list(object({ 4 | firstName = string 5 | lastName = string 6 | })) 7 | } 8 | 9 | variable "domain" { 10 | type = string 11 | } 12 | 13 | variable "full_module_path" { 14 | description = "The absolute path to the module" 15 | type = string 16 | } -------------------------------------------------------------------------------- /quickstarts/103-demo-tenant/modules/groups/main.tf: -------------------------------------------------------------------------------- 1 | # Create an Azure AD group with configurable display name and description 2 | resource "azuread_group" "maker_group" { 3 | display_name = var.group_name 4 | description = var.group_description 5 | security_enabled = true 6 | members = var.user_ids 7 | } 8 | 9 | # Output the object ID of the created group 10 | output "group_id" { 11 | value = azuread_group.maker_group.object_id 12 | } 13 | -------------------------------------------------------------------------------- /quickstarts/103-demo-tenant/modules/groups/variables.tf: -------------------------------------------------------------------------------- 1 | # Variable for the group name 2 | variable "group_name" { 3 | description = "The display name of the Azure AD group" 4 | type = string 5 | } 6 | 7 | # Variable for the group description 8 | variable "group_description" { 9 | description = "The description of the Azure AD group" 10 | type = string 11 | } 12 | 13 | variable "user_ids" { 14 | type = list(string) 15 | } 16 | -------------------------------------------------------------------------------- /quickstarts/103-demo-tenant/modules/licenses/main.tf: -------------------------------------------------------------------------------- 1 | # Retrieve an access token using an external PowerShell script 2 | data "external" "access_token" { 3 | program = ["pwsh", "./get_graph_access_token.ps1"] 4 | } 5 | 6 | # Fetch the assigned licenses for a specific group from Microsoft Graph API 7 | data "http" "group_assigned_plans" { 8 | url = "https://graph.microsoft.com/v1.0/groups/${var.group_id}/assignedLicenses" 9 | request_headers = { 10 | Authorization = "Bearer ${data.external.access_token.result.accessToken}" 11 | } 12 | depends_on = [data.external.access_token] 13 | } 14 | 15 | # Convert the list of license SKUs to a comma-separated string 16 | locals { 17 | license_skus_string = join(",", var.license_skus) 18 | group_assigned_plans_list = jsondecode(data.http.group_assigned_plans.response_body).value 19 | } 20 | 21 | # Determine which SKUs are missing from the group's assigned licenses 22 | data "null_data_source" "missing_skus" { 23 | inputs = { 24 | license_skus_string = local.license_skus_string 25 | group_assigned_plans = jsonencode(local.group_assigned_plans_list) 26 | } 27 | 28 | depends_on = [data.http.group_assigned_plans] 29 | } 30 | 31 | data "null_data_source" "missing_skus_processed" { 32 | inputs = { 33 | missing_skus = join(",", [for sku in split(",", data.null_data_source.missing_skus.inputs.license_skus_string) : sku if !(contains([for plan in jsondecode(data.null_data_source.missing_skus.inputs.group_assigned_plans) : plan.skuId], sku))]) 34 | } 35 | } 36 | 37 | # Execute the script if there are missing SKUs 38 | resource "null_resource" "assign_license" { 39 | provisioner "local-exec" { 40 | command = "./assign_license.ps1 -groupId '${var.group_id}' -licenseSkus '${data.null_data_source.missing_skus_processed.inputs.missing_skus}'" 41 | interpreter = ["pwsh","-Command"] 42 | on_failure = fail 43 | } 44 | 45 | depends_on = [data.null_data_source.missing_skus_processed] 46 | } 47 | 48 | output "missing_skus" { 49 | value = split(",", data.null_data_source.missing_skus_processed.inputs.missing_skus) 50 | } 51 | -------------------------------------------------------------------------------- /quickstarts/103-demo-tenant/modules/licenses/variables.tf: -------------------------------------------------------------------------------- 1 | variable "group_id" { 2 | type = string 3 | } 4 | 5 | variable "license_skus" { 6 | type = list(string) 7 | } -------------------------------------------------------------------------------- /quickstarts/103-demo-tenant/modules/users/main.tf: -------------------------------------------------------------------------------- 1 | # Configure the Azure Active Directory provider 2 | provider "azuread" { 3 | tenant_id = var.tenant_id 4 | } 5 | 6 | # Generate random passwords for each user 7 | resource "random_password" "user_passwords" { 8 | for_each = { for user in var.users : "${user.firstName}.${user.lastName}" => user } 9 | length = 24 10 | special = true 11 | upper = true 12 | lower = true 13 | numeric = true 14 | override_special = "_%@#&*()-_=+[]{}|;:,.<>?" 15 | } 16 | 17 | # Create Azure AD users with the generated passwords 18 | resource "azuread_user" "users" { 19 | for_each = { for user in var.users : "${user.firstName}.${user.lastName}" => user } 20 | 21 | user_principal_name = "${each.value.firstName}.${each.value.lastName}@${var.domain}" 22 | display_name = "${each.value.firstName} ${each.value.lastName}" 23 | mail_nickname = "${each.value.firstName}.${each.value.lastName}" 24 | given_name = each.value.firstName 25 | surname = each.value.lastName 26 | usage_location = "US" # Change this to the desired location 27 | password = random_password.user_passwords[each.key].result 28 | force_password_change = true 29 | } 30 | 31 | # Output the object IDs of the created users 32 | output "user_ids" { 33 | value = [for user in azuread_user.users : user.object_id] 34 | } -------------------------------------------------------------------------------- /quickstarts/103-demo-tenant/modules/users/variables.tf: -------------------------------------------------------------------------------- 1 | variable "tenant_id" { 2 | type = string 3 | } 4 | 5 | variable "users" { 6 | type = list(object({ 7 | firstName = string 8 | lastName = string 9 | })) 10 | } 11 | 12 | variable "domain" { 13 | type = string 14 | } 15 | -------------------------------------------------------------------------------- /quickstarts/103-demo-tenant/sample.tfvars.txt: -------------------------------------------------------------------------------- 1 | # Sample values for the group name and description 2 | group_name = "Makers" 3 | group_description = "Group for low code makers" -------------------------------------------------------------------------------- /quickstarts/103-demo-tenant/users.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "firstName": "Adele", 4 | "lastName": "Vance" 5 | }, 6 | { 7 | "firstName": "Avery", 8 | "lastName": "Smith" 9 | }, 10 | { 11 | "firstName": "Alan", 12 | "lastName": "Steiener" 13 | }, 14 | { 15 | "firstName": "Debra", 16 | "lastName": "Monjeau" 17 | }, 18 | { 19 | "firstName": "Dominique", 20 | "lastName": "Linna" 21 | }, 22 | { 23 | "firstName": "Derya", 24 | "lastName": "Ozcan" 25 | }, 26 | { 27 | "firstName": "Dylan", 28 | "lastName": "Williams" 29 | }, 30 | { 31 | "firstName": "Eka", 32 | "lastName": "Siahaan" 33 | }, 34 | { 35 | "firstName": "Ji-an", 36 | "lastName": "Lee" 37 | }, 38 | { 39 | "firstName": "Joni", 40 | "lastName": "Sherman" 41 | }, 42 | { 43 | "firstName": "Kai", 44 | "lastName": "Carter" 45 | }, 46 | { 47 | "firstName": "Kim", 48 | "lastName": "Rocha" 49 | }, 50 | { 51 | "firstName": "Luca", 52 | "lastName": "Richter" 53 | }, 54 | { 55 | "firstName": "Max", 56 | "lastName": "Morin" 57 | }, 58 | { 59 | "firstName": "Lee", 60 | "lastName": "Gu" 61 | }, 62 | { 63 | "firstName": "Nayan", 64 | "lastName": "Mittal" 65 | }, 66 | { 67 | "firstName": "Peyton", 68 | "lastName": "Davis" 69 | }, 70 | { 71 | "firstName": "Robin", 72 | "lastName": "Danielsen" 73 | }, 74 | { 75 | "firstName": "Rene", 76 | "lastName": "Pavlic" 77 | }, 78 | { 79 | "firstName": "Ronak", 80 | "lastName": "Gupta" 81 | }, 82 | { 83 | "firstName": "Sascha", 84 | "lastName": "Lange" 85 | }, 86 | { 87 | "firstName": "Sonu", 88 | "lastName": "Jain" 89 | }, 90 | { 91 | "firstName": "Sheetal", 92 | "lastName": "Parmar" 93 | }, 94 | { 95 | "firstName": "Vanja", 96 | "lastName": "Rozman" 97 | } 98 | ] -------------------------------------------------------------------------------- /quickstarts/103-demo-tenant/variables.tf: -------------------------------------------------------------------------------- 1 | variable "group_name" { 2 | description = "The display name of the Azure AD group" 3 | type = string 4 | } 5 | 6 | variable "group_description" { 7 | description = "The description of the Azure AD group" 8 | type = string 9 | } -------------------------------------------------------------------------------- /quickstarts/201-D365-finance-environment/README.md: -------------------------------------------------------------------------------- 1 | 2 | # D365 Finance Deployment (201 level) 3 | 4 | This Terraform module aims to provide a template for automating and standardizing the deployment and management of D365 Finance environments. 5 | It utilizes the deployment model outlined at: [Unified admin experience for finance and operations apps](https://learn.microsoft.com/power-platform/admin/unified-experience/finance-operations-apps-overview). 6 | 7 | ## Prerequisites 8 | 9 | - Service Principal or User Account with permissions configured as referenced in [this provider's user documentation](https://microsoft.github.io/terraform-provider-power-platform#authentication) . 10 | - A properly assigned D365 license (for example, a Dynamics 365 Finance or Dynamics 365 Supply Chain Management license). 11 | For more information on the new license requirements, see: [Transition from an environment-slot purchasing model to a capacity-based model](https://learn.microsoft.com/power-platform/admin/unified-experience/finance-operations-apps-overview#transition-from-an-environment-slot-purchasing-model-to-a-capacity-based-model). 12 | - At least 1 gigabyte of available Operations and Dataverse database capacities 13 | 14 | ## Example Files 15 | 16 | The example files can be found in `quickstarts/quickstarts/201-D365-finance-environment` 17 | 18 | ## Terraform Version Constraints 19 | 20 | - `>= 1.5` 21 | 22 | ## Provider Requirements 23 | 24 | - **power-platform (`microsoft/power-platform`):** (any version) 25 | 26 | ## Input Variables 27 | 28 | | Name | Description | Type | Default | Required | 29 | |------|-------------|------|---------|:--------:| 30 | | `client_id` | The client ID of the service principal (app registration) | string | `null` | true | 31 | | `currency_code` | The desired Currency Code for the environment, such as 'USD' | string | `null` | true | 32 | | `d365_finance_environment_name` | The name of the D365 Finance environment, such as 'd365fin-dev1 | string | `null` | true | 33 | | `domain` | The domain of the environment, such as 'd365fin-dev1' | string | `null` | true | 34 | | `environment_type` | The type of environment to deploy, such as 'Sandbox' | string | `null` | true | 35 | | `language_code` | The desired Language Code for the environment, such as '1033' (U.S. english) | string | `null` | true | 36 | | `location` | The region where the environment will be deployed, such as 'unitedstates' | string | `null` | true | 37 | | `secret` | The client secret of the service principal (app registration) | string | `null` | true | 38 | | `security_group_id` | The security group the environment will be associated with, a GUID. Can be set to 00000000-0000-0000-0000-000000000000 to indicate no security group restricting Dataverse access. | string | `null` | true | 39 | | `tenant_id` | The Entra (AAD) tenant id of service principal or user | string | `null` | true | 40 | 41 | ## Output Values 42 | 43 | | Name | Description | 44 | |------|-------------| 45 | | `id` | Unique identifier of the environment | 46 | | `linked_app_id` | Unique identifier of the linked D365 Finance app | 47 | | `linked_app_type` | Type of the linked D365 app | 48 | | `linked_app_url` | URL of the linked D365 Finance app | 49 | | `name` | Display name of the environment | 50 | | `url` | URL of the environment | 51 | 52 | ## Resources 53 | 54 | - `power-platform_environment.xpp-dev1` from `power-platform` 55 | 56 | ## Usage 57 | 58 | Include this module in your Terraform scripts as follows: 59 | 60 | ```hcl 61 | 62 | module "d365_finance_environment" { 63 | source = "./modules/201-D365-finance-environment" 64 | client_id = "Your App Registration ID (GUID) here" 65 | secret = "Your App Registration Secret here" 66 | tenant_id = "Your Entra (Azure) Tenant ID (GUID) here" 67 | d365_finance_environment_name = "d365fin-dev1" 68 | location = "unitedstates" 69 | language_code = "1033" 70 | currency_code = "USD" 71 | environment_type = "Sandbox" 72 | security_group_id = "00000000-0000-0000-0000-000000000000" 73 | domain = "d365fin-dev1" 74 | } 75 | 76 | ``` 77 | 78 | ## Detailed Behavior 79 | 80 | ### Power Platform Environment 81 | 82 | This module creates a Power Platform environment using a combination of the parameters in the terraform files as well as the default settings specified by the 'templates' property. 83 | 84 | ### Dynamics 365 Finance Environment 85 | 86 | This module creates a Dynamics 365 Finance development environment using the default settings specified by the 'templates' and 'template_metadata' properties. 87 | 88 | ## Limitations and Considerations 89 | 90 | - Provisioning can take over an hour, so refrain from rerunning the same environment creation Terraform files more than hourly, as this will cause unexpected behavior. 91 | - This quickstart is configured for service-principal-based authentication as outlined in [this provider's user documentation](https://microsoft.github.io/terraform-provider-power-platform#authentication). 92 | If you plan to use user-based authentication, you will need to ensure that the selected user is assigned a D365 Finance or D365 Supply Chain Management license as outlined in the 93 | [Unified Admin Experience Overview](https://learn.microsoft.com/power-platform/admin/unified-experience/finance-operations-apps-overview). 94 | 95 | ## Additional Resources 96 | 97 | - [Power Platform Admin Documentation](https://learn.microsoft.com/power-platform/admin/) 98 | -------------------------------------------------------------------------------- /quickstarts/201-D365-finance-environment/README.md.tmpl: -------------------------------------------------------------------------------- 1 | # D365 Finance Deployment (201 level) 2 | 3 | This Terraform module aims to provide a template for automating and standardizing the deployment and management of D365 Finance environments. 4 | It utilizes the deployment model outlined at: [Unified admin experience for finance and operations apps](https://learn.microsoft.com/power-platform/admin/unified-experience/finance-operations-apps-overview). 5 | 6 | ## Prerequisites 7 | 8 | - Service Principal or User Account with permissions configured as referenced in [this provider's user documentation](https://microsoft.github.io/terraform-provider-power-platform#authentication) . 9 | - A properly assigned D365 license (for example, a Dynamics 365 Finance or Dynamics 365 Supply Chain Management license). 10 | For more information on the new license requirements, see: [Transition from an environment-slot purchasing model to a capacity-based model](https://learn.microsoft.com/power-platform/admin/unified-experience/finance-operations-apps-overview#transition-from-an-environment-slot-purchasing-model-to-a-capacity-based-model). 11 | - At least 1 gigabyte of available Operations and Dataverse database capacities 12 | 13 | ## Example Files 14 | 15 | The example files can be found in `quickstarts/quickstarts/201-D365-finance-environment` 16 | 17 | ## Terraform Version Constraints 18 | 19 | - `>= 1.5` 20 | 21 | ## Provider Requirements 22 | 23 | - **power-platform (`microsoft/power-platform`):** (any version) 24 | 25 | ## Input Variables 26 | 27 | | Name | Description | Type | Default | Required | 28 | |------|-------------|------|---------|:--------:| 29 | | `client_id` | The client ID of the service principal (app registration) | string | `null` | true | 30 | | `currency_code` | The desired Currency Code for the environment, such as 'USD' | string | `null` | true | 31 | | `d365_finance_environment_name` | The name of the D365 Finance environment, such as 'd365fin-dev1 | string | `null` | true | 32 | | `domain` | The domain of the environment, such as 'd365fin-dev1' | string | `null` | true | 33 | | `environment_type` | The type of environment to deploy, such as 'Sandbox' | string | `null` | true | 34 | | `language_code` | The desired Language Code for the environment, such as '1033' (U.S. english) | string | `null` | true | 35 | | `location` | The region where the environment will be deployed, such as 'unitedstates' | string | `null` | true | 36 | | `secret` | The client secret of the service principal (app registration) | string | `null` | true | 37 | | `security_group_id` | The security group the environment will be associated with, a GUID. Can be set to 00000000-0000-0000-0000-000000000000 to indicate no security group restricting Dataverse access. | string | `null` | true | 38 | | `tenant_id` | The Entra (AAD) tenant id of service principal or user | string | `null` | true | 39 | 40 | ## Output Values 41 | 42 | | Name | Description | 43 | |------|-------------| 44 | | `id` | Unique identifier of the environment | 45 | | `linked_app_id` | Unique identifier of the linked D365 Finance app | 46 | | `linked_app_type` | Type of the linked D365 app | 47 | | `linked_app_url` | URL of the linked D365 Finance app | 48 | | `name` | Display name of the environment | 49 | | `url` | URL of the environment | 50 | 51 | ## Resources 52 | 53 | - `power-platform_environment.xpp-dev1` from `power-platform` 54 | 55 | ## Usage 56 | 57 | Include this module in your Terraform scripts as follows: 58 | 59 | ```hcl 60 | 61 | module "d365_finance_environment" { 62 | source = "./modules/201-D365-finance-environment" 63 | client_id = "Your App Registration ID (GUID) here" 64 | secret = "Your App Registration Secret here" 65 | tenant_id = "Your Entra (Azure) Tenant ID (GUID) here" 66 | d365_finance_environment_name = "d365fin-dev1" 67 | location = "unitedstates" 68 | language_code = "1033" 69 | currency_code = "USD" 70 | environment_type = "Sandbox" 71 | security_group_id = "00000000-0000-0000-0000-000000000000" 72 | domain = "d365fin-dev1" 73 | } 74 | 75 | ``` 76 | 77 | ## Detailed Behavior 78 | 79 | ### Power Platform Environment 80 | 81 | This module creates a Power Platform environment using a combination of the parameters in the terraform files as well as the default settings specified by the 'templates' property. 82 | 83 | ### Dynamics 365 Finance Environment 84 | 85 | This module creates a Dynamics 365 Finance development environment using the default settings specified by the 'templates' and 'template_metadata' properties. 86 | 87 | ## Limitations and Considerations 88 | 89 | - Provisioning can take over an hour, so refrain from rerunning the same environment creation Terraform files more than hourly, as this will cause unexpected behavior. 90 | - This quickstart is configured for service-principal-based authentication as outlined in [this provider's user documentation](https://microsoft.github.io/terraform-provider-power-platform#authentication). 91 | If you plan to use user-based authentication, you will need to ensure that the selected user is assigned a D365 Finance or D365 Supply Chain Management license as outlined in the 92 | [Unified Admin Experience Overview](https://learn.microsoft.com/power-platform/admin/unified-experience/finance-operations-apps-overview). 93 | 94 | ## Additional Resources 95 | 96 | - [Power Platform Admin Documentation](https://learn.microsoft.com/power-platform/admin/) 97 | -------------------------------------------------------------------------------- /quickstarts/201-D365-finance-environment/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.5" 3 | required_providers { 4 | powerplatform = { 5 | source = "microsoft/power-platform" 6 | version = ">=3.3.0" 7 | } 8 | } 9 | } 10 | 11 | provider "powerplatform" { 12 | use_cli = true 13 | } 14 | 15 | 16 | resource "powerplatform_environment" "xpp-dev1" { 17 | display_name = var.d365_finance_environment_name 18 | location = var.location 19 | environment_type = var.environment_type 20 | dataverse = { 21 | templates = ["D365_FinOps_Finance"] 22 | template_metadata = "{\"PostProvisioningPackages\": [{ \"applicationUniqueName\": \"msdyn_FinanceAndOperationsProvisioningAppAnchor\",\n \"parameters\": \"DevToolsEnabled=true|DemoDataEnabled=true\"\n }\n ]\n }" 23 | language_code = var.language_code 24 | currency_code = var.currency_code 25 | security_group_id = var.security_group_id 26 | } 27 | } -------------------------------------------------------------------------------- /quickstarts/201-D365-finance-environment/outputs.tf: -------------------------------------------------------------------------------- 1 | output "id" { 2 | description = "Unique identifier of the environment" 3 | value = powerplatform_environment.xpp-dev1.id 4 | } 5 | output "name" { 6 | description = "Display name of the environment" 7 | value = powerplatform_environment.xpp-dev1.display_name 8 | } 9 | 10 | output "url" { 11 | description = "URL of the environment" 12 | value = powerplatform_environment.xpp-dev1.dataverse.url 13 | } 14 | 15 | output "linked_app_type" { 16 | description = "Type of the linked D365 app" 17 | value = powerplatform_environment.xpp-dev1.dataverse.linked_app_type 18 | } 19 | 20 | output "linked_app_id" { 21 | description = "Unique identifier of the linked D365 Finance app" 22 | value = powerplatform_environment.xpp-dev1.dataverse.linked_app_id 23 | } 24 | 25 | output "linked_app_url" { 26 | description = "URL of the linked D365 Finance app" 27 | value = powerplatform_environment.xpp-dev1.dataverse.linked_app_url 28 | } 29 | -------------------------------------------------------------------------------- /quickstarts/201-D365-finance-environment/variables.tf: -------------------------------------------------------------------------------- 1 | variable "d365_finance_environment_name" { 2 | description = "The name of the D365 Finance environment, such as 'd365fin-dev1" 3 | type = string 4 | validation { 5 | condition = length(var.d365_finance_environment_name) <= 20 6 | error_message = "The length of the d365_finance_environment_name property cannot exceed 20 characters for D365 Finance environment deployments." 7 | } 8 | } 9 | variable "location" { 10 | description = "The region where the environment will be deployed, such as 'unitedstates'" 11 | type = string 12 | } 13 | variable "language_code" { 14 | description = "The desired Language Code for the environment, such as '1033' (U.S. english)" 15 | type = string 16 | } 17 | variable "currency_code" { 18 | description = "The desired Currency Code for the environment, such as 'USD'" 19 | type = string 20 | } 21 | #Options are "Sandbox", "Production", "Trial", "Developer" 22 | variable "environment_type" { 23 | description = "The type of environment to deploy, such as 'Sandbox'" 24 | type = string 25 | validation { 26 | condition = contains(["Sandbox", "Production", "Trial", "Developer"], var.environment_type) 27 | error_message = "The selected value for environment_type is not in the list of allowed values in variables.tf" 28 | } 29 | } 30 | variable "domain" { 31 | description = "The domain of the environment, such as 'd365fin-dev1'" 32 | type = string 33 | validation { 34 | condition = length(var.domain) <= 32 35 | error_message = "The length of the domain property cannot exceed 32 characters for D365 Finance environment deployments." 36 | } 37 | } 38 | variable "security_group_id" { 39 | description = "The security group ID for the environment, such as '00000000-0000-0000-0000-000000000000'" 40 | type = string 41 | } 42 | -------------------------------------------------------------------------------- /quickstarts/300-pac-checkov/README.md: -------------------------------------------------------------------------------- 1 | 2 | # PaC with checkov example (300 level) 3 | 4 | This example demonstrates how to use checkov to detect security or policy misconfigurations using terraform to deploy an Azure storage account and a blob container. 5 | 6 | # Policy as Code (PaC) 7 | 8 | Policy As Code is a DevOps Practice that verify and enforce security configurations when you deploy Infrastructure, can also ben use to force the configuration of other policy checks like enable encryption of disks on virtual machines or the replication of storage or databases on different regions. 9 | 10 | Policy as code is a practice of managing and defining policies in a code-like format, which can be version-controlled, tested, and automatically deployed. This approach allows for the automation of policy enforcement and compliance, making it easier to manage and maintain policies across an organization. 11 | 12 | ## Prerequisites 13 | 14 | - Checkov and Python are already configured on the dev container of this repo, we recommend using the dev container. 15 | - Entra ID Tenant 16 | - Azure subscription where the terraform state will be stored 17 | - Power Platform environment (optional) 18 | 19 | ## Example Files 20 | 21 | The example files can be found in `quickstarts/300-pac-checkov` 22 | 23 | ## Provider Requirements 24 | 25 | The Terraform plugins or "providers" that this IaC deployment requires are: 26 | 27 | - **azurecaf (`aztfmod/azurecaf`):** `>=1.2.26` 28 | 29 | - **azurerm (`hashicorp/azurerm`):** `>=3.74.0` 30 | 31 | ## Input Variables 32 | 33 | | Name | Description | Type | Default | Required | 34 | |------|-------------|------|---------|:--------:| 35 | | `base_name` | The base name which should be used for all resources name | string | `"pac"` | false | 36 | | `location` | The Azure region where the resources in this example should be created | string | `null` | true | 37 | | `prefix` | The prefix which should be used for all resources name | string | `"pac"` | false | 38 | | `subscription_id` | The subscription ID of the service principal with on-premise data gateway admin permissions | string | `null` | true | 39 | | `tenant_id` | The tenant ID of service principal or user at Power Platform | string | `null` | true | 40 | 41 | ## Resources 42 | 43 | - `azurecaf_name.rg` from `azurecaf` 44 | 45 | - `azurerm_resource_group.rg` from `azurerm` 46 | 47 | ## PaC example steps 48 | 49 | - Clone the repo on your computer. 50 | - Select the PaC example branch 51 | - Start the dev container, this process can take a couple of minutes. 52 | - We include a basic terraform storage account creation example in `quickstarts/300-pac-checkov/main.tf` 53 | - Look to the file and identify the resources that you can deploy with this terraform file. 54 | - Run the azure login command and authenticate with azure services. 55 | - Open the `quickstarts/300-pac-checkov/tfcheckov.sh` and look to the commands that are required to run checkov and terraform. 56 | - Run checkov, we create the tfcheckov.sh (described above), to do that on the dev container console execute the “sh” file “./tfcheckov.sh”. 57 | 58 | ```bash 59 | ./tfcheckov.sh 60 | ``` 61 | 62 | - The first time that you run the command you will not receive any warning this is expected because the storage account is commented. 63 | - Uncomment the storage account part, save the file and run checkov again. 64 | - Now you will receive checkov warnings, for example one could be to enable GRS for the storage account. 65 | - Enable GRS, change the account_replication_type from "LRS" to be "GRS", save the file and run checkov again. 66 | 67 | ```hcl 68 | resource "azurerm_storage_account" "storage_account" { 69 | name = azurecaf_name.storage_account_name.result 70 | resource_group_name = azurerm_resource_group.rg.name 71 | location = azurerm_resource_group.rg.location 72 | account_tier = "Standard" 73 | account_replication_type = "GRS" 74 | } 75 | 76 | ``` 77 | 78 | ```bash 79 | ./tfcheckov.sh 80 | ``` 81 | 82 | The goal is to see the checkov behavior and solve a couple of those warnings. 83 | 84 | ## Additional Resources 85 | 86 | - [Checkov Terraform scans](https://www.checkov.io/5.Policy%20Index/terraform.html) 87 | -------------------------------------------------------------------------------- /quickstarts/300-pac-checkov/README.md.tmpl: -------------------------------------------------------------------------------- 1 | # PaC with checkov example (300 level) 2 | 3 | This example demonstrates how to use checkov to detect security or policy misconfigurations using terraform to deploy an Azure storage account and a blob container. 4 | 5 | # Policy as Code (PaC) 6 | 7 | Policy As Code is a DevOps Practice that verify and enforce security configurations when you deploy Infrastructure, can also ben use to force the configuration of other policy checks like enable encryption of disks on virtual machines or the replication of storage or databases on different regions. 8 | 9 | Policy as code is a practice of managing and defining policies in a code-like format, which can be version-controlled, tested, and automatically deployed. This approach allows for the automation of policy enforcement and compliance, making it easier to manage and maintain policies across an organization. 10 | 11 | ## Prerequisites 12 | 13 | - Checkov and Python are already configured on the dev container of this repo, we recommend using the dev container. 14 | - Entra ID Tenant 15 | - Azure subscription where the terraform state will be stored 16 | - Power Platform environment (optional) 17 | 18 | {{ .ModuleDetails }} 19 | 20 | ## PaC example steps 21 | 22 | - Clone the repo on your computer. 23 | - Select the PaC example branch 24 | - Start the dev container, this process can take a couple of minutes. 25 | - We include a basic terraform storage account creation example in `quickstarts/300-pac-checkov/main.tf` 26 | - Look to the file and identify the resources that you can deploy with this terraform file. 27 | - Run the azure login command and authenticate with azure services. 28 | - Open the `quickstarts/300-pac-checkov/tfcheckov.sh` and look to the commands that are required to run checkov and terraform. 29 | - Run checkov, we create the tfcheckov.sh (described above), to do that on the dev container console execute the “sh” file “./tfcheckov.sh”. 30 | 31 | ```bash 32 | ./tfcheckov.sh 33 | ``` 34 | 35 | - The first time that you run the command you will not receive any warning this is expected because the storage account is commented. 36 | - Uncomment the storage account part, save the file and run checkov again. 37 | - Now you will receive checkov warnings, for example one could be to enable GRS for the storage account. 38 | - Enable GRS, change the account_replication_type from "LRS" to be "GRS", save the file and run checkov again. 39 | 40 | ```hcl 41 | resource "azurerm_storage_account" "storage_account" { 42 | name = azurecaf_name.storage_account_name.result 43 | resource_group_name = azurerm_resource_group.rg.name 44 | location = azurerm_resource_group.rg.location 45 | account_tier = "Standard" 46 | account_replication_type = "GRS" 47 | } 48 | 49 | ``` 50 | 51 | ```bash 52 | ./tfcheckov.sh 53 | ``` 54 | 55 | The goal is to see the checkov behavior and solve a couple of those warnings. 56 | 57 | ## Additional Resources 58 | 59 | - [Checkov Terraform scans](https://www.checkov.io/5.Policy%20Index/terraform.html) 60 | -------------------------------------------------------------------------------- /quickstarts/300-pac-checkov/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | azurerm = { 4 | source = "hashicorp/azurerm" 5 | version = ">=3.74.0" 6 | } 7 | azurecaf = { 8 | source = "aztfmod/azurecaf" 9 | version = ">=1.2.26" 10 | } 11 | } 12 | } 13 | 14 | 15 | provider "azurerm" { 16 | features { 17 | resource_group { 18 | prevent_deletion_if_contains_resources = false 19 | } 20 | } 21 | } 22 | 23 | resource "azurecaf_name" "rg" { 24 | name = var.base_name 25 | resource_type = "azurerm_resource_group" 26 | prefixes = [var.prefix] 27 | random_length = 3 28 | clean_input = true 29 | } 30 | 31 | resource "azurerm_resource_group" "rg" { 32 | name = azurecaf_name.rg.result 33 | location = var.location 34 | } 35 | 36 | // The following section is commented with the objective of removing the comments and executing checkov, 37 | // after executing it we can see the tool's alerts. 38 | 39 | // Remove comments from the next section and run checkov to view and resolve the alerts. 40 | 41 | /* 42 | resource "azurecaf_name" "storage_account_name" { 43 | name = var.base_name 44 | resource_type = "azurerm_storage_account" 45 | prefixes = [var.prefix] 46 | random_length = 3 47 | clean_input = true 48 | } 49 | 50 | resource "azurerm_storage_account" "storage_account" { 51 | name = azurecaf_name.storage_account_name.result 52 | resource_group_name = azurerm_resource_group.rg.name 53 | location = azurerm_resource_group.rg.location 54 | account_tier = "Standard" 55 | account_replication_type = "LRS" 56 | } 57 | 58 | resource "azurerm_storage_container" "storage_container_installs" { 59 | name = "installs" 60 | storage_account_name = azurerm_storage_account.storage_account.name 61 | container_access_type = "blob" 62 | } 63 | 64 | */ -------------------------------------------------------------------------------- /quickstarts/300-pac-checkov/tfcheckov.sh: -------------------------------------------------------------------------------- 1 | echo "##############################################################################" 2 | echo "# Commands nedded to check the Terraform code lab #" 3 | echo "##############################################################################" 4 | echo "terraform init" 5 | terraform init -upgrade 6 | echo "terraform fmt -recursive" 7 | terraform fmt 8 | echo "terraform validate" 9 | terraform validate 10 | echo "terraform plan -var-file=local.tfvars -out tf.plan" 11 | terraform plan -var-file=local.tfvars -out tf.plan 12 | echo "terraform show -json tf.plan > tf.json # required for checkov" 13 | terraform show -json tf.plan > tf.json 14 | echo "checkov excecution" 15 | checkov -f tf.json 16 | echo "##############################################################################" -------------------------------------------------------------------------------- /quickstarts/300-pac-checkov/variables.tf: -------------------------------------------------------------------------------- 1 | variable "location" { 2 | description = "The Azure region where the resources in this example should be created" 3 | type = string 4 | } 5 | 6 | variable "prefix" { 7 | description = "The prefix which should be used for all resources name" 8 | default = "pac" 9 | type = string 10 | } 11 | 12 | variable "base_name" { 13 | description = "The base name which should be used for all resources name" 14 | default = "pac" 15 | type = string 16 | } 17 | 18 | variable "subscription_id" { 19 | description = "The subscription ID of the service principal with on-premise data gateway admin permissions" 20 | type = string 21 | } 22 | 23 | variable "tenant_id" { 24 | description = "The tenant ID of service principal or user at Power Platform" 25 | type = string 26 | } 27 | -------------------------------------------------------------------------------- /quickstarts/301-sap-gateway/README.md.tmpl: -------------------------------------------------------------------------------- 1 | # SAP Connectivity Runtime Setup (301 level) 2 | 3 | This Terraform module aims to provide a fully managed infrastructure that integrates Microsoft's Power Platform and Azure services with SAP Systems. Utilizing `azurerm` and `azurecaf` Terraform providers, this module encapsulates best practices and serves as a reference architecture for scalable, reliable, and manageable cloud infrastructure. 4 | 5 | In order to provide connectivity between SAP and Microsoft services, it is required to install a set runtime software and also setup particular configuration. 6 | This document is a guide to setup a Terraform script to provision the Virtual Machine and all the requirements to connect the SHIR (Self-hosted Integration Runtime), 7 | the Microsoft Gateway and SAP .NET Connector. 8 | 9 | ## Prerequisites and Preparation 10 | 11 | - Azure subscription 12 | - All the credentials for Azure resources creation. 13 | - Service Principal or User Account with permissions configured as referenced in [the provider's user documentation](https://microsoft.github.io/terraform-provider-power-platform#authentication), 14 | we provide a terraform script to create a Service Principal with the required configuration in the folder `./gateway-principal`. 15 | 16 | 17 | ### SAP Systems 18 | 19 | For the execution of this Terraform script, you do not need the SAP credentials or the application server information. However, it is important to know how to connect to the SAP system to provide proper information to the script. 20 | 21 | The scenario covered by this script is the SAP system installed on-premisses on Azure, and you cannot use a public address, so you will need to provide the subnet ID where the SAP system is installed. 22 | 23 | The subnet ID is available at the JSON view of the virtual network, in the parameter id. It is expected something like below: 24 | 25 | `/subscriptions/abababab-12ab-ab00-82e2-aa00babab102/resourceGroups/resouce-group-name/providers/Microsoft.Network/virtualNetworks/VNet-name/subnets/default` 26 | 27 | ### Storage Account Preparation 28 | 29 | Before you execute the script, you need to upload the SAP .NET Connector executable file the folder `./storage-account/sapnco` and rename to `sapnco.exe` (check below for more information). 30 | 31 | ### SHIR Nodes Preparation 32 | 33 | Make sure there is not any node assigned to the self-hosted integration runtime at Synapse or ADF. 34 | 35 | {{ .ModuleDetails }} 36 | 37 | ## Usage 38 | 39 | The entire script is required for the proper installation, unless you decide to create any one of the resources separatelly. 40 | 41 | 42 | > [!NOTE] 43 | > To use Terraform commands against your Azure subscription, you must first authenticate Terraform to that subscription. The article [Authenticate Terraform to Azure](https://learn.microsoft.com/en-us/azure/developer/terraform/authenticate-to-azure?tabs=bash) 44 | > provides guidance on how to do this, remember to choose the correct subscription. 45 | > It is highly recommended that you use the [VS Code Dev Container](https://code.visualstudio.com/docs/devcontainers/containers) to run the scripts and terraform modules in this repository. 46 | 47 | You have to execute the normal Terraform commands: 48 | 49 | ``terraform init -upgrade 50 | 51 | ``terraform plan -var-file=local.tfvars 52 | 53 | ``terraform apply -var-file=local.tfvars 54 | 55 | ``terraform destroy -var-file=local.tfvars 56 | 57 | ## Terraform Version Constraints 58 | 59 | - azurerm `>=3.74.0` 60 | - azurecaf `>=1.2.26` 61 | 62 | ## Detailed Behavior 63 | 64 | ### On-Premise Data Gateway 65 | 66 | A Windows Virtual Machine is created with all the software connectors required to connect on-premise data gateway and self-hosted integration runtime. 67 | 68 | It is the list of software installed on the Virtual Machine: 69 | 70 | #### PowerShell 7 71 | 72 | It is required to execute the script for the on-premise data gateway installation. After VM creation, the [script](./gateway-vm/scripts/ps7-setup.ps1) download and install PowerShell 7. 73 | 74 | #### Java Runtime 75 | 76 | It is required for SHIR runtime and SAP data flows. Check the [prerequisites](https://learn.microsoft.com/en-us/azure/data-factory/create-self-hosted-integration-runtime?tabs=data-factory#prerequisites) in the documentation for more details. 77 | 78 | #### Microsoft Self-Hosted Integration Runtime (SHIR) 79 | 80 | It is the runtime used to connect the VM to SHIR in Synapse/ADF/Fabric. Check the [documentation](https://learn.microsoft.com/en-us/azure/data-factory/create-self-hosted-integration-runtime) for more details. 81 | 82 | #### SAP Connector for .Net 83 | 84 | It is the runtime used to connect the VM to SAP system. You need to [download the MSI file](https://support.sap.com/en/product/connectors/msnet.html) and upload to the folder mentioned above. The installation provided in this guide, follows this [documentation](https://learn.microsoft.com/en-us/azure/data-factory/sap-change-data-capture-shir-preparation). 85 | 86 | #### On-Premises Data Gateway 87 | 88 | It is the runtime used to connect to the Power Platform connectors (e.g. SAP ERP). Here is some references used to created the script: 89 | 90 | - [Learn how to install On-premises data gateway for Azure Analysis Services | Microsoft Learn](https://learn.microsoft.com/en-us/azure/analysis-services/analysis-services-gateway-install?tabs=azure-powershell) 91 | - [Data Gateway Documentation](https://learn.microsoft.com/en-us/powershell/module/datagateway/?view=datagateway-ps) 92 | 93 | ### Power Platform Resources 94 | 95 | A PowerApps Environment is created. 96 | 97 | ### Network 98 | 99 | All resources are provisioned within the same Azure Virtual Network where the SAP System is installed (`sap_subnet_id` input parameter), ensuring that they can communicate securely without exposure to the public internet. 100 | 101 | ## Limitations and Considerations 102 | 103 | - Due to Power Platform limitations, certain resources may not fully support Terraform's state management. 104 | - Make sure to set appropriate RBAC for Azure and Power Platform resources. 105 | - This module is provided as a sample only and is not intended for production use without further customization. 106 | 107 | ## Additional Resources 108 | 109 | - [Power Platform Admin Documentation](https://learn.microsoft.com/en-us/power-platform/admin/) 110 | - [Azure AD Terraform Provider](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/guides/service_principal_configuration) 111 | - 112 | -------------------------------------------------------------------------------- /quickstarts/301-sap-gateway/gateway-principal/README.md: -------------------------------------------------------------------------------- 1 | # Service Principal Module for SAP Gateway (200 level) 2 | 3 | This Module contains the Terraform code required to create a Service Principal and application in Azure Active Directory. This Service Principal will be used to authenticate the SAP Gateway. 4 | 5 | More information on the configurations required for this service principal can be found in the [the provider's user documentation](https://microsoft.github.io/terraform-provider-power-platform#authentication). 6 | 7 | > [!NOTE] 8 | > To use Terraform commands against your Azure subscription, you must first authenticate Terraform to that subscription. The article [Authenticate Terraform to Azure](https://learn.microsoft.com/en-us/azure/developer/terraform/authenticate-to-azure?tabs=bash) 9 | > provides guidance on how to do this, remember to choose the correct subscription. 10 | > It is highly recommended that you use the [VS Code Dev Container](https://code.visualstudio.com/docs/devcontainers/containers) to run the scripts and terraform modules in this repository. 11 | 12 | You have to execute the normal Terraform commands: 13 | 14 | ``terraform init -upgrade 15 | 16 | ``terraform plan -var-file=local.tfvars 17 | 18 | ``terraform apply -var-file=local.tfvars 19 | 20 | ``terraform destroy -var-file=local.tfvars 21 | 22 | ## Terraform Version Constraints 23 | 24 | - azurerm `>=3.74.0` 25 | - azurecaf `>=1.2.26` 26 | 27 | ## Prerequisites 28 | 29 | - Entra ID Tenant 30 | - Azure subscription where the terraform state will be stored 31 | - Power Platform environment (optional) 32 | 33 | {{ .ModuleDetails }} 34 | 35 | ## Limitations and Considerations 36 | 37 | - Due to Power Platform limitations, certain resources may not fully support Terraform's state management. 38 | - Make sure to set appropriate RBAC for Azure and Power Platform resources. 39 | - This module is provided as a sample only and is not intended for production use without further customization. 40 | 41 | ## Additional Resources 42 | 43 | - [Power Platform Admin Documentation](https://learn.microsoft.com/en-us/power-platform/admin/) 44 | - [Azure AD Terraform Provider](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/guides/service_principal_configuration) 45 | - 46 | -------------------------------------------------------------------------------- /quickstarts/301-sap-gateway/gateway-principal/README.md.tmpl: -------------------------------------------------------------------------------- 1 | # Service Principal Module for SAP Gateway (200 level) 2 | 3 | This Module contains the Terraform code required to create a Service Principal and application in Azure Active Directory. This Service Principal will be used to authenticate the SAP Gateway. 4 | 5 | More information on the configurations required for this service principal can be found in the [the provider's user documentation](https://microsoft.github.io/terraform-provider-power-platform#authentication). 6 | 7 | > [!NOTE] 8 | > To use Terraform commands against your Azure subscription, you must first authenticate Terraform to that subscription. The article [Authenticate Terraform to Azure](https://learn.microsoft.com/en-us/azure/developer/terraform/authenticate-to-azure?tabs=bash) 9 | > provides guidance on how to do this, remember to choose the correct subscription. 10 | > It is highly recommended that you use the [VS Code Dev Container](https://code.visualstudio.com/docs/devcontainers/containers) to run the scripts and terraform modules in this repository. 11 | 12 | You have to execute the normal Terraform commands: 13 | 14 | ``terraform init -upgrade 15 | 16 | ``terraform plan -var-file=local.tfvars 17 | 18 | ``terraform apply -var-file=local.tfvars 19 | 20 | ``terraform destroy -var-file=local.tfvars 21 | 22 | ## Terraform Version Constraints 23 | 24 | - azurerm `>=3.74.0` 25 | - azurecaf `>=1.2.26` 26 | 27 | 28 | ## Prerequisites 29 | 30 | - Entra ID Tenant 31 | - Azure subscription where the terraform state will be stored 32 | - Power Platform environment (optional) 33 | 34 | {{ .ModuleDetails }} 35 | 36 | ## Limitations and Considerations 37 | 38 | - Due to Power Platform limitations, certain resources may not fully support Terraform's state management. 39 | - Make sure to set appropriate RBAC for Azure and Power Platform resources. 40 | - This module is provided as a sample only and is not intended for production use without further customization. 41 | 42 | ## Additional Resources 43 | 44 | - [Power Platform Admin Documentation](https://learn.microsoft.com/en-us/power-platform/admin/) 45 | - [Azure AD Terraform Provider](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/guides/service_principal_configuration) 46 | - 47 | -------------------------------------------------------------------------------- /quickstarts/301-sap-gateway/gateway-principal/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.5" 3 | required_providers { 4 | azurerm = { 5 | source = "hashicorp/azurerm" 6 | } 7 | azuread = { 8 | source = "hashicorp/azuread" 9 | } 10 | } 11 | } 12 | 13 | provider "azuread" { 14 | } 15 | 16 | data "azuread_application_published_app_ids" "well_known" { 17 | 18 | } 19 | 20 | data "azuread_service_principal" "powerbi" { 21 | client_id = data.azuread_application_published_app_ids.well_known.result.PowerBiService 22 | #use_existing = true 23 | } 24 | 25 | resource "azuread_application" "gateway_application" { 26 | display_name = "Gateway Application" 27 | 28 | required_resource_access { 29 | resource_app_id = data.azuread_application_published_app_ids.well_known.result.PowerBiService 30 | 31 | resource_access { 32 | id = data.azuread_service_principal.powerbi.app_role_ids["Tenant.Read.All"] 33 | type = "Role" 34 | } 35 | 36 | resource_access { 37 | id = data.azuread_service_principal.powerbi.app_role_ids["Tenant.ReadWrite.All"] 38 | type = "Role" 39 | } 40 | } 41 | } 42 | 43 | resource "azuread_service_principal" "gateway_principal" { 44 | client_id = azuread_application.gateway_application.client_id 45 | description = "Gateway Principal" 46 | } 47 | 48 | resource "azuread_service_principal_password" "gateway_principal_password" { 49 | service_principal_id = azuread_service_principal.gateway_principal.id 50 | } 51 | -------------------------------------------------------------------------------- /quickstarts/301-sap-gateway/gateway-principal/outputs.tf: -------------------------------------------------------------------------------- 1 | output "auth_client_id" { 2 | value = azuread_service_principal.gateway_principal.id 3 | description = "output password" 4 | } 5 | 6 | output "auth_client_secret" { 7 | value = azuread_service_principal_password.gateway_principal_password.value 8 | description = "output password" 9 | sensitive = true 10 | } 11 | -------------------------------------------------------------------------------- /quickstarts/301-sap-gateway/gateway-vm/java-runtime-setup/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.5" 3 | required_providers { 4 | azurerm = { 5 | source = "hashicorp/azurerm" 6 | } 7 | } 8 | } 9 | 10 | resource "azurerm_gallery_application" "java-igl-app" { 11 | name = "java-setup.ps1" 12 | gallery_id = var.sig_id 13 | location = var.region 14 | supported_os_type = "Windows" 15 | } 16 | 17 | resource "azurerm_gallery_application_version" "java-igl-app-version" { 18 | name = "0.0.1" 19 | gallery_application_id = azurerm_gallery_application.java-igl-app.id 20 | location = var.region 21 | 22 | manage_action { 23 | install = "C:\\powershell7\\7\\pwsh.exe -ExecutionPolicy Unrestricted -File java-setup.ps1 -Verb RunAs" 24 | remove = "echo" # Uninstall script is not applicable. 25 | } 26 | 27 | source { 28 | media_link = var.java_setup_link 29 | } 30 | 31 | target_region { 32 | name = var.region 33 | regional_replica_count = 1 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /quickstarts/301-sap-gateway/gateway-vm/java-runtime-setup/outputs.tf: -------------------------------------------------------------------------------- 1 | output "java_runtime_version_id" { 2 | value = azurerm_gallery_application_version.java-igl-app-version.id 3 | } 4 | -------------------------------------------------------------------------------- /quickstarts/301-sap-gateway/gateway-vm/java-runtime-setup/variables.tf: -------------------------------------------------------------------------------- 1 | variable "prefix" { 2 | description = "The prefix which should be used for all resources in this example" 3 | default = "opdgw" 4 | type = string 5 | } 6 | 7 | variable "base_name" { 8 | description = "The base name which should be used for all resources in this example" 9 | default = "AzureSAPIntegration" 10 | type = string 11 | } 12 | 13 | variable "resource_group_name" { 14 | description = "The name of the resource group where the resources in this example should be created" 15 | type = string 16 | } 17 | 18 | variable "region" { 19 | description = "The Azure region where the resources in this example should be created" 20 | type = string 21 | } 22 | 23 | variable "sig_id" { 24 | description = "The id of the shared image gallery where the image should be created" 25 | type = string 26 | } 27 | 28 | variable "java_setup_link" { 29 | description = "The Blob link to the Java Runtime installation file" 30 | type = string 31 | } 32 | -------------------------------------------------------------------------------- /quickstarts/301-sap-gateway/gateway-vm/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.5" 3 | required_providers { 4 | azurerm = { 5 | source = "hashicorp/azurerm" 6 | } 7 | azurecaf = { 8 | source = "aztfmod/azurecaf" 9 | version = ">=1.2.26" 10 | } 11 | } 12 | } 13 | 14 | resource "azurecaf_name" "sig" { 15 | name = var.base_name 16 | resource_type = "azurerm_shared_image_gallery" 17 | prefixes = [var.prefix] 18 | random_length = 3 19 | clean_input = true 20 | } 21 | 22 | resource "azurerm_shared_image_gallery" "sig" { 23 | name = azurecaf_name.sig.result 24 | resource_group_name = var.resource_group_name 25 | location = var.region 26 | tags = var.tags 27 | } 28 | 29 | # Create PowerShell 7 version in Shared Image Gallery 30 | module "ps7-setup" { 31 | source = "./ps7-setup" 32 | prefix = var.prefix 33 | base_name = var.base_name 34 | resource_group_name = var.resource_group_name 35 | region = var.region 36 | sig_id = azurerm_shared_image_gallery.sig.id 37 | ps7_setup_link = var.ps7_setup_link 38 | } 39 | 40 | # Create Java Runtime version in Shared Image Gallery 41 | module "java-runtime-setup" { 42 | source = "./java-runtime-setup" 43 | prefix = var.prefix 44 | base_name = var.base_name 45 | resource_group_name = var.resource_group_name 46 | region = var.region 47 | sig_id = azurerm_shared_image_gallery.sig.id 48 | java_setup_link = var.java_setup_link 49 | 50 | depends_on = [module.ps7-setup] 51 | } 52 | 53 | # Create SAP NCo version in Shared Image Gallery 54 | module "sapnco_install" { 55 | source = "./sapnco-install" 56 | prefix = var.prefix 57 | base_name = var.base_name 58 | resource_group_name = var.resource_group_name 59 | region = var.region 60 | sig_id = azurerm_shared_image_gallery.sig.id 61 | sapnco_install_link = var.sapnco_install_link 62 | 63 | depends_on = [module.ps7-setup, module.java-runtime-setup] 64 | } 65 | 66 | # Install Script for Runtime configuration 67 | module "runtime-setup" { 68 | source = "./runtime-setup" 69 | prefix = var.prefix 70 | base_name = var.base_name 71 | resource_group_name = var.resource_group_name 72 | region = var.region 73 | sig_id = azurerm_shared_image_gallery.sig.id 74 | runtime_setup_link = var.runtime_setup_link 75 | 76 | depends_on = [module.ps7-setup, module.java-runtime-setup, module.sapnco_install] 77 | } 78 | 79 | # There is an issue in the resource for naming Key Vaults that is preventing to proper naming 80 | # Name and prefixes are not working properly, with random part 81 | resource "random_string" "vm-opgw-suffix" { 82 | length = 5 83 | upper = false 84 | numeric = false 85 | special = false 86 | } 87 | 88 | #concatenate the base name with the random suffix 89 | locals { 90 | vm_opgw_name = "${var.base_name}-${random_string.vm-opgw-suffix.result}" 91 | } 92 | 93 | resource "azurerm_windows_virtual_machine" "vm-opgw" { 94 | name = local.vm_opgw_name 95 | location = var.region 96 | resource_group_name = var.resource_group_name 97 | network_interface_ids = [var.nic_id] 98 | 99 | identity { 100 | type = "SystemAssigned" 101 | } 102 | 103 | # rest of the resource block 104 | size = "Standard_D4s_v5" 105 | admin_username = var.vm_user 106 | admin_password = var.vm_pwd 107 | computer_name = "vmopgw" 108 | enable_automatic_updates = true 109 | bypass_platform_safety_checks_on_user_schedule_enabled = false 110 | patch_assessment_mode = "ImageDefault" 111 | patch_mode = "AutomaticByOS" 112 | encryption_at_host_enabled = var.encryption_at_host_enabled # "true" Enable encryption at host, need's to configure the disk encryption set 113 | #checkov:skip=CKV_AZURE_151:encryption_at_host_enabled is set to false do to we dont have permisions to enable that feature. 114 | allow_extension_operations = var.allow_extension_operations # "false" This feature needs to be turn to true to allow the VM to install extensions. 115 | #checkov:skip=CKV_AZURE_50:allow_extension_operations is set to false to install SAP SW. 116 | 117 | os_disk { 118 | caching = "ReadWrite" 119 | storage_account_type = "Standard_LRS" 120 | disk_size_gb = 128 121 | name = "myosdisk1" 122 | } 123 | 124 | source_image_reference { 125 | publisher = "MicrosoftWindowsServer" 126 | offer = "WindowsServer" 127 | sku = "2022-datacenter-smalldisk" 128 | version = "latest" 129 | } 130 | 131 | # Setup PowerShell 7 132 | gallery_application { 133 | version_id = module.ps7-setup.powershell_version_id 134 | order = 1 135 | } 136 | 137 | # Setup Java Runtime 138 | gallery_application { 139 | version_id = module.java-runtime-setup.java_runtime_version_id 140 | order = 2 141 | } 142 | 143 | # Install SAP NCo 144 | gallery_application { 145 | version_id = module.sapnco_install.sapnco_install_version_id 146 | order = 3 147 | } 148 | 149 | # Setup Runtime configuration 150 | gallery_application { 151 | version_id = module.runtime-setup.runtime_version_id 152 | order = 4 153 | } 154 | tags = var.tags 155 | } 156 | 157 | # Create a virtual machine extension to run the runtime-setup.ps1 script 158 | # This script uses the VM Principal ID to access the Key Vault and retrieve the secrets 159 | # VM Principal ID is only available after the VM is created 160 | resource "azurerm_virtual_machine_extension" "runtime-setup" { 161 | name = "runtime-setup" 162 | virtual_machine_id = azurerm_windows_virtual_machine.vm-opgw.id 163 | publisher = "Microsoft.Compute" 164 | type = "CustomScriptExtension" 165 | type_handler_version = "1.10" 166 | 167 | settings = < tf.json # required for checkov" 13 | terraform show -json tf.plan > tf.json 14 | echo "checkov excecution" 15 | checkov -f tf.json 16 | -------------------------------------------------------------------------------- /quickstarts/301-sap-gateway/tfclean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo "#######################################" 3 | echo "## Remove Terraform files ##" 4 | echo "#######################################" 5 | # Prompt the user for input 6 | echo "This SH will detroy your Terraform configuration and will delete terraform files." 7 | echo "If you want to remove terraform files please enter 'yes' or 'no':" 8 | read user_input 9 | # Check the user's response 10 | if [ "$user_input" = "yes" ]; then 11 | echo "You chose 'yes'." 12 | echo "rm .terraform.lock.hcl" 13 | rm .terraform.lock.hcl 14 | echo "rm terraform.tfstate" 15 | rm terraform.tfstate 16 | echo "rm terraform.tfstate.backup" 17 | rm terraform.tfstate.backup 18 | echo "rm out.plan" 19 | rm out.plan 20 | rm /mnt/c/VSCODE/IaC-TF-Provider-BAPImain/power-platform-terraform-quickstarts/quickstarts/301-sap-gateway/.terraform/modules/modules.json 21 | rm -rf /mnt/c/VSCODE/IaC-TF-Provider-BAPImain/power-platform-terraform-quickstarts/quickstarts/301-sap-gateway/.terraform/modules/registry.terraform.io/hashicorp/random/3.1.0 22 | rm -rf /mnt/c/VSCODE/IaC-TF-Provider-BAPImain/power-platform-terraform-quickstarts/quickstarts/301-sap-gateway/.terraform/providers/registry.terraform.io/hashicorp/random/3.6.0/linux_amd64/terraform-provider-random_v3.6.0_x5 23 | rm -rf /mnt/c/VSCODE/IaC-TF-Provider-BAPImain/power-platform-terraform-quickstarts/quickstarts/301-sap-gateway/.terraform/providers/registry.terraform.io/hashicorp/azurerm/3.97.1/linux_amd64/terraform-provider-azurerm_v3.97.1_x5 24 | rm -rf /mnt/c/VSCODE/IaC-TF-Provider-BAPImain/power-platform-terraform-quickstarts/quickstarts/301-sap-gateway/.terraform/providers/aztfmod/azurecaf/1.2.28/linux_amd64/terraform-provider-azurecaf_v1.2.28_x5 25 | rm -rf /mnt/c/VSCODE/IaC-TF-Provider-BAPImain/power-platform-terraform-quickstarts/quickstarts/301-sap-gateway/.terraform/providers/aztfmod/azurecaf/1.2.28/linux_amd64/README.md 26 | rm -rd /mnt/c/VSCODE/IaC-TF-Provider-BAPImain/power-platform-terraform-quickstarts/quickstarts/301-sap-gateway/.terraform 27 | elif [ "$user_input" = "no" ]; then 28 | echo "You chose 'no'." 29 | else 30 | echo "Invalid input. Please enter 'yes' or 'no'." 31 | fi 32 | echo "#######################################" -------------------------------------------------------------------------------- /quickstarts/301-sap-gateway/tfplan.sh: -------------------------------------------------------------------------------- 1 | echo "########################################################" 2 | echo "## SAP GW Terraform deployment "TERRAFORM PLAN" ####" 3 | echo "########################################################" 4 | echo "terraform init -upgrade" 5 | terraform init -upgrade 6 | echo "terraform fmt -recursive" 7 | terraform fmt -recursive 8 | echo "terraform validate" 9 | terraform validate 10 | echo "terraform plan -var-file=local.tfvars -out out.plan" 11 | terraform plan -var-file=local.tfvars -out out.plan 12 | -------------------------------------------------------------------------------- /quickstarts/301-sap-gateway/variables.tf: -------------------------------------------------------------------------------- 1 | variable "prefix" { 2 | description = "The prefix which should be used for all resources name" 3 | default = "opdgw" 4 | type = string 5 | } 6 | 7 | variable "base_name" { 8 | description = "The base name which should be used for all resources name" 9 | default = "AzureSAPIntegration" 10 | type = string 11 | } 12 | 13 | variable "client_id_pp" { 14 | description = "The client ID / app ID of the service principal with Power Platform admin permissions" 15 | type = string 16 | } 17 | 18 | variable "secret_pp" { 19 | description = "The secret of the service principal with Power Platform admin permissions" 20 | sensitive = true 21 | type = string 22 | } 23 | 24 | variable "tenant_id_pp" { 25 | description = "The tenant ID of service principal or user at Power Platform" 26 | type = string 27 | } 28 | 29 | variable "client_id_gw" { 30 | description = "The client ID / app ID of the service principal where the on-premise data gateway admin permissions" 31 | type = string 32 | } 33 | 34 | variable "secret_gw" { 35 | description = "The secret of the service principal with on-premise data gateway admin permissions" 36 | sensitive = true 37 | type = string 38 | } 39 | variable "tenant_id_gw" { 40 | description = "The tenant ID of service principal or user" 41 | type = string 42 | } 43 | 44 | variable "subscription_id_gw" { 45 | description = "The subscription ID of the service principal with on-premise data gateway admin permissions" 46 | type = string 47 | } 48 | 49 | variable "region_gw" { 50 | description = "The Azure region where the resources in this example should be created" 51 | type = string 52 | } 53 | 54 | variable "sap_subnet_id" { 55 | description = "The SAP system subnet ID" 56 | type = string 57 | } 58 | 59 | variable "user_id_admin_pp" { 60 | description = "The user ID to be assigned as Admin role of the Power Platform" 61 | type = string 62 | } 63 | 64 | /* 65 | variable "ps7_setup_link" { 66 | description = "The Blob link to the PowerShell 7 installation file" 67 | type = string 68 | } 69 | 70 | variable "java_setup_link" { 71 | description = "The Blob link to the Java Runtime installation file" 72 | type = string 73 | } 74 | variable "sapnco_install_link" { 75 | description = "The Blob link to the SAP NCo installation file" 76 | type = string 77 | } 78 | 79 | variable "runtime_setup_link" { 80 | description = "The Blob link to the runtime setup script" 81 | type = string 82 | } 83 | */ 84 | 85 | variable "shir_key" { 86 | description = "Value of the secret name for the IR key" 87 | type = string 88 | } 89 | 90 | variable "gateway_name" { 91 | description = "The name of the gateway to be created on Power Platform" 92 | type = string 93 | } 94 | 95 | variable "recover_key_gw" { 96 | description = "The recovery key of the gateway" 97 | type = string 98 | } 99 | 100 | variable "environment" { 101 | description = "tag name of the environment like dev, stg, prod" 102 | type = string 103 | } 104 | 105 | variable "tags" { 106 | description = "A map of tags to add to all resources" 107 | type = map(string) 108 | default = { 109 | environment = "dev" 110 | department = "IT" 111 | costcenter = "12345" 112 | } 113 | } -------------------------------------------------------------------------------- /tools/quickstartgen/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Updating Documentation 3 | 4 | User documentation markdown files for each quickstart like: "/XXX-quickstart-name/README.md" are auto-generated by the [quickstartgen tool](/tools/quickstartgen/quickstartgen.go). 5 | 6 | Do not manually edit the markdown files. If you need to edit documentation edit the template file related to your quickstart README.MD, for example if you want to eddit the "101-hello-power-platform" quickstart you will need to edit the file: "\quickstarts\101-hello-power-platform\README.md.tmpl". 7 | 8 | To run the "quickstartergen.go" tool type the following bash command: 9 | 10 | ```bash 11 | go run quickstartgen.go 12 | ``` 13 | -------------------------------------------------------------------------------- /tools/quickstartgen/README.md.tmpl: -------------------------------------------------------------------------------- 1 | # Updating Documentation 2 | 3 | User documentation markdown files for each quickstart like: "/XXX-quickstart-name/README.md" are auto-generated by the [quickstartgen tool](/tools/quickstartgen/quickstartgen.go). 4 | 5 | Do not manually edit the markdown files. If you need to edit documentation edit the template file related to your quickstart README.MD, for example if you want to eddit the "101-hello-power-platform" quickstart you will need to edit the file: "\quickstarts\101-hello-power-platform\README.md.tmpl". 6 | 7 | To run the "quickstartergen.go" tool type the following bash command: 8 | 9 | ```bash 10 | go run quickstartgen.go 11 | ``` 12 | -------------------------------------------------------------------------------- /tools/quickstartgen/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/microsoft/terraform-provider-power-platform/tools/quickstartgen 2 | 3 | go 1.21.0 4 | 5 | require github.com/hashicorp/terraform-config-inspect v0.0.0-20231204233900-a34142ec2a72 6 | 7 | require ( 8 | github.com/agext/levenshtein v1.2.3 // indirect 9 | github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect 10 | github.com/google/go-cmp v0.6.0 // indirect 11 | github.com/hashicorp/hcl v1.0.0 // indirect 12 | github.com/hashicorp/hcl/v2 v2.19.1 // indirect 13 | github.com/mitchellh/go-wordwrap v1.0.1 // indirect 14 | github.com/zclconf/go-cty v1.14.2 // indirect 15 | golang.org/x/text v0.14.0 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /tools/quickstartgen/go.sum: -------------------------------------------------------------------------------- 1 | github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= 2 | github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= 3 | github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= 4 | github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= 8 | github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= 9 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 10 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 11 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 12 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 13 | github.com/hashicorp/hcl/v2 v2.19.1 h1://i05Jqznmb2EXqa39Nsvyan2o5XyMowW5fnCKW5RPI= 14 | github.com/hashicorp/hcl/v2 v2.19.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE= 15 | github.com/hashicorp/terraform-config-inspect v0.0.0-20231204233900-a34142ec2a72 h1:nZ5gGjbe5o7XUu1d7j+Y5Ztcxlp+yaumTKH9i0D3wlg= 16 | github.com/hashicorp/terraform-config-inspect v0.0.0-20231204233900-a34142ec2a72/go.mod h1:l8HcFPm9cQh6Q0KSWoYPiePqMvRFenybP1CH2MjKdlg= 17 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 18 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 19 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 20 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 21 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 22 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 23 | github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= 24 | github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= 25 | github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= 26 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 27 | github.com/zclconf/go-cty v1.14.2 h1:kTG7lqmBou0Zkx35r6HJHUQTvaRPr5bIAf3AoHS0izI= 28 | github.com/zclconf/go-cty v1.14.2/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= 29 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 30 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 31 | -------------------------------------------------------------------------------- /tools/quickstartgen/mainreadme.md.tmpl: -------------------------------------------------------------------------------- 1 | # Power Platform Terraform Quickstarts 2 | 3 | This repository contains example terraform modules that can be used to quickly deploy Power Platform environments and other Azure resources. The modules are intended to demonstrate some common scenarios where managing Power Platform resources along side Azure, [Entra](https://entra.microsoft.com), or other resources can be facilitated with the [Terraform Provider for Power Platform](https://github.com/microsoft/terraform-provider-power-platform). The modules are examples and are not intended to be used in production environments without modification. 4 | 5 | This repository contains scripts quickly build out a new tenant and configure it to allow you to deploy and manage Power Platform environments along side other Azure resources. The scripts assume that you are starting with a completely new tenant with an empty Azure subscription. This is a template repository that is intended to let you fork and customize the Power Platform/Azure resources to accommodate your own needs. 6 | 7 | ## Prerequisites 8 | 9 | * Microsoft Tenant that you have `global admin` or `user administrator` permissions in 10 | * Azure subscription in the tenant that you have `owner` permissions in 11 | * A fork of this GitHub repository that you have `admin` permissions in 12 | 13 | ### Tooling 14 | 15 | > [!NOTE] 16 | > The following tooling is pre-installed in the [VS Code Dev Container](https://code.visualstudio.com/docs/devcontainers/containers) and it is highly recommended that you use the Dev Container to run the scripts and terraform modules in this repository: 17 | 18 | * [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/) 19 | * [Terraform CLI](https://developer.hashicorp.com/terraform/cli) 20 | * [GitHub CLI](https://cli.github.com/) 21 | * [Docker](https://www.docker.com/) 22 | 23 | ### Terraform Providers 24 | 25 | The following terraform providers are used in this repository: 26 | 27 | * [Power-Platform](https://github.com/microsoft/terraform-provider-power-platform) 28 | * [AzureRM](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs) 29 | * [AzureAD](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs) 30 | * [GitHub](https://registry.terraform.io/providers/integrations/github/latest/docs) 31 | * [Random](https://registry.terraform.io/providers/hashicorp/random/latest/docs) 32 | * [Null](https://registry.terraform.io/providers/hashicorp/null/latest/docs) 33 | 34 | ## Getting Started 35 | 36 | The example terraform modules are intended to be run by GitHub Actions, however there are several steps that need to be run locally by an administrator in order to create the resources the terraform modules need to use. The following steps should be run in order: 37 | 38 | 1. [Bootstrap](bootstrap/README.md) this will create and configure the prerequisites that are needed to run the quickstart examples. 39 | 2. Try out any of the following **Quickstart Examples** 40 | 41 | ## Quickstart Examples 42 | 43 | {{ .ExamplesList }} 44 | 45 | Quickstarts use the Power-Platform provider, documentation can be found [here](https://microsoft.github.io/terraform-provider-power-platform/). 46 | -------------------------------------------------------------------------------- /tools/quickstartgen/mainreadmetemplate.md.tmpl: -------------------------------------------------------------------------------- 1 | 2 | {{- if .QuickStarts}} 3 | 4 | {{- range .QuickStarts }} 5 | * [{{ .Name }}](./quickstarts/{{.Name}}/README.md) 6 | {{- end}}{{end}} -------------------------------------------------------------------------------- /tools/quickstartgen/quickstart.md.tmpl: -------------------------------------------------------------------------------- 1 | ## Example Files 2 | 3 | The example files can be found in {{ .Path | relativePath | tt}} 4 | 5 | {{- if .RequiredCore}} 6 | 7 | ## Terraform Version Constraints 8 | 9 | {{- range .RequiredCore }} 10 | - {{ tt . }} 11 | {{- end}}{{end}} 12 | 13 | {{- if .RequiredProviders}} 14 | 15 | ## Provider Requirements 16 | 17 | The Terraform plugins or "providers" that this IaC deployment requires are: 18 | 19 | 20 | {{- range $name, $req := .RequiredProviders }} 21 | 22 | - **{{ $name }}{{ if $req.Source }} ({{ $req.Source | tt }}){{ end }}:** {{ if $req.VersionConstraints }}`{{ commas $req.VersionConstraints}}`{{ else }}(any version){{ end }} 23 | 24 | {{- end}}{{end}} 25 | 26 | {{- if .Variables}} 27 | 28 | ## Input Variables 29 | 30 | | Name | Description | Type | Default | Required | 31 | |------|-------------|------|---------|:--------:| 32 | {{- range .Variables }} 33 | | {{ tt .Name }} | {{ .Description }} | {{ .Type }} | {{ json .Default | tt }} | {{ .Required }} | 34 | {{- end}}{{end}} 35 | 36 | {{- if .Outputs}} 37 | 38 | ## Output Values 39 | 40 | | Name | Description | 41 | |------|-------------| 42 | {{- range .Outputs }} 43 | | {{ tt .Name }} | {{ .Description }} | 44 | {{- end}}{{end}} 45 | 46 | {{- if .ManagedResources}} 47 | 48 | ## Resources 49 | 50 | {{- range .ManagedResources }} 51 | 52 | - {{ printf "%s.%s" .Type .Name | tt }} from {{ tt .Provider.Name }} 53 | 54 | {{- end}}{{end}} 55 | 56 | {{- if .DataResources}} 57 | 58 | ## Data Sources 59 | 60 | {{- range .DataResources }} 61 | 62 | - {{ printf "data.%s.%s" .Type .Name | tt }} from {{ tt .Provider.Name }} 63 | 64 | {{- end}}{{end}} 65 | 66 | {{- if .ModuleCalls}} 67 | 68 | ## Child Modules 69 | 70 | {{- range .ModuleCalls }} 71 | 72 | - {{ tt .Name }} from {{ tt .Source }}{{ if .Version }} ({{ tt .Version }}){{ end }} 73 | 74 | {{- end}}{{end}} 75 | 76 | {{- if .Diagnostics}} 77 | 78 | ## Problems 79 | 80 | {{- range .Diagnostics }} 81 | 82 | ## {{ severity .Severity }}{{ .Summary }}{{ if .Pos }} 83 | 84 | (at {{ tt .Pos.Filename }} line {{ .Pos.Line }}{{ end }}) 85 | {{ if .Detail }} 86 | {{ .Detail }} 87 | {{- end }} 88 | 89 | {{- end}}{{end}} -------------------------------------------------------------------------------- /tools/quickstartgen/quickstartgen: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/power-platform-terraform-quickstarts/7c0273dda635b694aff8ae35eaa1a966521da4f7/tools/quickstartgen/quickstartgen -------------------------------------------------------------------------------- /tools/quickstartgen/quickstartgen.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "log" 6 | "os" 7 | 8 | "github.com/hashicorp/terraform-config-inspect/tfconfig" 9 | 10 | "encoding/json" 11 | "io" 12 | "path/filepath" 13 | "strings" 14 | "text/template" 15 | ) 16 | 17 | const workspacePath = "/workspaces/power-platform-terraform-quickstarts/" 18 | const mainReadmeTemplatePath = workspacePath + "/tools/quickstartgen/mainreadmetemplate.md.tmpl" 19 | const mainReadmePath = workspacePath + "/tools/quickstartgen/mainreadme.md.tmpl" 20 | 21 | var temaplteFuncHelpers = template.FuncMap{ 22 | "tt": func(s string) string { 23 | return "`" + s + "`" 24 | }, 25 | "commas": func(s []string) string { 26 | return strings.Join(s, ", ") 27 | }, 28 | "json": func(v interface{}) (string, error) { 29 | j, err := json.Marshal(v) 30 | return string(j), err 31 | }, 32 | "severity": func(s tfconfig.DiagSeverity) string { 33 | switch s { 34 | case tfconfig.DiagError: 35 | return "Error: " 36 | case tfconfig.DiagWarning: 37 | return "Warning: " 38 | default: 39 | return "" 40 | } 41 | }, 42 | "relativePath": func(path string) string { 43 | rp, _ := filepath.Rel(workspacePath, path) 44 | return rp 45 | }, 46 | } 47 | 48 | type ExamplesData struct { 49 | QuickStarts []QuickStart 50 | } 51 | 52 | type QuickStart struct { 53 | Name string 54 | } 55 | 56 | func main() { 57 | GenerateQuickstarts() 58 | GenerateMainReadme() 59 | GenerateTools() 60 | } 61 | 62 | func GenerateMainReadme() { 63 | os.Stdout.WriteString("Generating main README\n") 64 | mainReadmeTemplate, err := os.ReadFile(mainReadmeTemplatePath) 65 | if err != nil { 66 | log.Fatal(err) 67 | } 68 | 69 | var readmeTemplate bytes.Buffer 70 | readmeTemplate.WriteString("\n") 71 | err = RenderReadme(&readmeTemplate, mainReadmePath, struct { 72 | ExamplesList string 73 | }{ 74 | ExamplesList: string(mainReadmeTemplate), 75 | }) 76 | if err != nil { 77 | panic(err) 78 | } 79 | 80 | var readmeMarkdown bytes.Buffer 81 | 82 | examplesPath := workspacePath + "/quickstarts/" 83 | examplesDir, err := os.Open(examplesPath) 84 | if err != nil { 85 | panic(err) 86 | } 87 | dirNames, err := examplesDir.Readdirnames(0) 88 | if err != nil { 89 | panic(err) 90 | } 91 | 92 | data := ExamplesData{} 93 | for _, dirName := range dirNames { 94 | data.QuickStarts = append(data.QuickStarts, QuickStart{Name: dirName}) 95 | } 96 | RenderMainReadmeMarkdown(&readmeMarkdown, readmeTemplate.String(), &data) 97 | 98 | os.WriteFile(filepath.Join(workspacePath, "README.md"), readmeMarkdown.Bytes(), 0644) 99 | } 100 | 101 | func GenerateQuickstarts() { 102 | os.Stdout.WriteString("Generating READMEs for quickstarts\n") 103 | 104 | const path = workspacePath + "/quickstarts/" 105 | quickstartTemplate, qerr := os.ReadFile(workspacePath + "/tools/quickstartgen/quickstart.md.tmpl") 106 | if qerr != nil { 107 | log.Fatal(qerr) 108 | } 109 | 110 | qsDir, err := os.Open(path) 111 | if err != nil { 112 | log.Fatal(err) 113 | } 114 | 115 | defer qsDir.Close() 116 | 117 | // Read directory entries 118 | fileInfos, err := qsDir.Readdir(-1) // Passing -1 to read all available entries 119 | if err != nil { 120 | log.Fatalf("Failed to read directory: %v", err) 121 | } 122 | 123 | // Iterate over each entry and print subdirectories 124 | for _, fileInfo := range fileInfos { 125 | if fileInfo.IsDir() { 126 | 127 | modulePath := filepath.Join(path, fileInfo.Name()) 128 | 129 | os.Stdout.WriteString("Generating README.md for " + modulePath + "\n") 130 | 131 | // Parse the terraform in the module 132 | module, diags := tfconfig.LoadModule(modulePath) 133 | if diags.HasErrors() { 134 | panic(diags) 135 | } 136 | 137 | // Parse the README.md.tmpl 138 | var readmeTemplate bytes.Buffer 139 | readmeTemplate.WriteString("\n") 140 | err := RenderReadme(&readmeTemplate, filepath.Join(path, fileInfo.Name(), "README.md.tmpl"), struct { 141 | ModuleDetails string 142 | ModuleName string 143 | }{ 144 | ModuleDetails: string(quickstartTemplate), 145 | ModuleName: fileInfo.Name(), 146 | }) 147 | if err != nil { 148 | panic(err) 149 | } 150 | 151 | var readmeMarkdown bytes.Buffer 152 | RenderQuickStartReadmeMarkdown(&readmeMarkdown, readmeTemplate.String(), module) 153 | 154 | os.WriteFile(filepath.Join(path, fileInfo.Name(), "README.md"), readmeMarkdown.Bytes(), 0644) 155 | } 156 | } 157 | } 158 | 159 | func RenderReadme(w io.Writer, templatePath string, data any) error { 160 | tmpl := template.Must(template.ParseFiles(templatePath)) 161 | return tmpl.Execute(w, data) 162 | } 163 | 164 | func RenderMainReadmeMarkdown(w io.Writer, markdownTemplate string, data *ExamplesData) error { 165 | tmpl := template.New("md") 166 | tmpl.Funcs(temaplteFuncHelpers) 167 | template.Must(tmpl.Parse(markdownTemplate)) 168 | return tmpl.Execute(w, data) 169 | } 170 | 171 | func RenderQuickStartReadmeMarkdown(w io.Writer, markdownTemplate string, module *tfconfig.Module) error { 172 | tmpl := template.New("md") 173 | tmpl.Funcs(temaplteFuncHelpers) 174 | template.Must(tmpl.Parse(markdownTemplate)) 175 | return tmpl.Execute(w, module) 176 | } 177 | 178 | func GenerateTools() { 179 | os.Stdout.WriteString("Generating READMEs for quickstartgen\n") 180 | 181 | const path = workspacePath + "/tools/" 182 | quickstartTemplate, qerr := os.ReadFile(workspacePath + "/tools/quickstartgen/quickstart.md.tmpl") 183 | if qerr != nil { 184 | log.Fatal(qerr) 185 | } 186 | 187 | qsDir, err := os.Open(path) 188 | if err != nil { 189 | log.Fatal(err) 190 | } 191 | 192 | defer qsDir.Close() 193 | 194 | // Read directory entries 195 | fileInfos, err := qsDir.Readdir(-1) // Passing -1 to read all available entries 196 | if err != nil { 197 | log.Fatalf("Failed to read directory: %v", err) 198 | } 199 | 200 | // Iterate over each entry and print subdirectories 201 | for _, fileInfo := range fileInfos { 202 | if fileInfo.IsDir() { 203 | 204 | modulePath := filepath.Join(path, fileInfo.Name()) 205 | 206 | os.Stdout.WriteString("Generating README.md for " + modulePath + "\n") 207 | 208 | // Parse the terraform in the module 209 | module, diags := tfconfig.LoadModule(modulePath) 210 | if diags.HasErrors() { 211 | panic(diags) 212 | } 213 | 214 | // Parse the README.md.tmpl 215 | var readmeTemplate bytes.Buffer 216 | readmeTemplate.WriteString("\n") 217 | err := RenderReadme(&readmeTemplate, filepath.Join(path, fileInfo.Name(), "README.md.tmpl"), struct { 218 | ModuleDetails string 219 | ModuleName string 220 | }{ 221 | ModuleDetails: string(quickstartTemplate), 222 | ModuleName: fileInfo.Name(), 223 | }) 224 | if err != nil { 225 | panic(err) 226 | } 227 | 228 | var readmeMarkdown bytes.Buffer 229 | RenderQuickStartReadmeMarkdown(&readmeMarkdown, readmeTemplate.String(), module) 230 | 231 | os.WriteFile(filepath.Join(path, fileInfo.Name(), "README.md"), readmeMarkdown.Bytes(), 0644) 232 | } 233 | } 234 | } 235 | --------------------------------------------------------------------------------