├── .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 | 
43 |
44 | The Terraform Plan output will also be added to your pull request as a comment:
45 |
46 | 
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 | 
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 | 
30 |
31 | The Terraform Plan output will also be added to your pull request as a comment:
32 |
33 | 
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 | 
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 |
--------------------------------------------------------------------------------