diff --git a/.checkov_config.yaml b/.checkov_config.yaml new file mode 100644 index 0000000..523fb60 --- /dev/null +++ b/.checkov_config.yaml @@ -0,0 +1,22 @@ +block-list-secret-scan: [] +branch: master +directory: +- ./ +download-external-modules: false +evaluate-variables: true +external-modules-download-path: .external_modules +framework: +- all +quiet: true +secrets-scan-file-type: [] +skip-check: +- CKV_GHA_3 +- CKV_AZURE_119 +# Check following rules after v4.0 +- CKV2_AZURE_18 +- CKV2_AZURE_1 +- CKV2_AZURE_10 +- CKV2_AZURE_12 +skip-framework: +- dockerfile +summary-position: top diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..dcd525c --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,26 @@ +{ + "image": "mcr.microsoft.com/azterraform:latest", + + "runArgs": [ + "--cap-add=SYS_PTRACE", + "--security-opt", + "seccomp=unconfined", + "--init", + "--network=host" + ], + + "mounts": [ "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind" ], + "customizations": { + "vscode": { + "settings": { + "go.toolsManagement.checkForUpdates": "local", + "go.useLanguageServer": true, + "go.goroot": "/usr/local/go" + }, + "extensions": [ + "hashicorp.terraform", + "golang.Go" + ] + } + } +} diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index 1e8c20b..0000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1,3 +0,0 @@ -os/ @metacpp -test/ @junyiyi -*.tf @tf-owner diff --git a/.github/ISSUE_TEMPLATE/Bug_Report.yml b/.github/ISSUE_TEMPLATE/Bug_Report.yml new file mode 100644 index 0000000..307790c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Bug_Report.yml @@ -0,0 +1,120 @@ +name: Bug Report +description: If something isn't working as expected. +labels: [bug] +body: + - type: markdown + attributes: + value: | + Thank you for taking the time to fill out a bug report. + + If you are not running the latest version of this module, please try to reproduce your bug with the latest version before opening an issue. + - type: checkboxes + attributes: + label: Is there an existing issue for this? + description: Please search to see if an issue already exists for the bug you encountered. + options: + - label: I have searched the existing issues + required: true + - type: dropdown + attributes: + label: Greenfield/Brownfield provisioning + description: Do you reproduce the bug with a new infrastructure provisioning (greenfield) or you need an existing infrastructure with an existing terraform state (brownfield) to reproduce the bug ? + multiple: false + options: + - greenfield + - brownfield + validations: + required: true + - type: input + id: terraform + attributes: + label: Terraform Version + description: Which Terraform version are you using? + placeholder: Example value, 1.2.8 + validations: + required: true + - type: input + id: module + attributes: + label: Module Version + description: Which module version are you using? + placeholder: Example value, 6.0.0 + validations: + required: true + - type: input + id: azurerm + attributes: + label: AzureRM Provider Version + description: Which AzureRM Provider version are you using? + placeholder: Example value, 3.21.1 + validations: + required: true + - type: input + id: resource + attributes: + label: Affected Resource(s)/Data Source(s) + description: Please list the affected resources and/or data sources. + placeholder: azurerm_XXXXX + validations: + required: true + - type: textarea + id: config + attributes: + label: Terraform Configuration Files + description: | + Please provide a minimal Terraform configuration that can reproduce the issue. + render: hcl + validations: + required: true + - type: textarea + id: tfvars + attributes: + label: tfvars variables values + description: | + Please provide the necessary tfvars variables values to reproduce the issue. Do not share secrets or sensitive information. + render: hcl + validations: + required: true + - type: textarea + id: debug + attributes: + label: Debug Output/Panic Output + description: | + For long debug logs please provide a link to a GitHub Gist containing the complete debug output. Please do NOT paste the debug output in the issue; just paste a link to the Gist. + + To obtain the debug output, see the [Terraform documentation on debugging](https://www.terraform.io/docs/internals/debugging.html). + render: shell + validations: + required: true + - type: textarea + id: expected + attributes: + label: Expected Behaviour + description: What should have happened? + - type: textarea + id: actual + attributes: + label: Actual Behaviour + description: What actually happened? + - type: textarea + id: reproduce + attributes: + label: Steps to Reproduce + description: | + Please list the steps required to reproduce the issue, e.g. + + 1. `terraform apply` + - type: input + id: facts + attributes: + label: Important Factoids + description: | + Are there anything atypical about your accounts that we should know? For example: Running in a Azure China/Germany/Government? + - type: textarea + id: references + attributes: + label: References + description: | + Information about referencing Github Issues: https://help.github.com/articles/basic-writing-and-formatting-syntax/#referencing-issues-and-pull-requests + + Are there any other GitHub issues (open or closed) or pull requests that should be linked here? Such as vendor documentation? diff --git a/.github/ISSUE_TEMPLATE/Feature_Request.yml b/.github/ISSUE_TEMPLATE/Feature_Request.yml new file mode 100644 index 0000000..d30bbdf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Feature_Request.yml @@ -0,0 +1,42 @@ +name: Feature Request +description: I have a suggestion (and might want to implement myself)! +title: "Support for [thing]" +body: + - type: checkboxes + attributes: + label: Is there an existing issue for this? + description: Please search to see if an issue already exists for the feature you are requesting. + options: + - label: I have searched the existing issues + required: true + - type: textarea + id: description + attributes: + label: Description + description: Please leave a helpful description of the feature request here. + validations: + required: true + - type: input + id: resource + attributes: + label: New or Affected Resource(s)/Data Source(s) + description: Please list the new or affected resources and/or data sources. + placeholder: azurerm_XXXXX + validations: + required: true + - type: textarea + id: config + attributes: + label: Potential Terraform Configuration + description: Please provide an example of what the enhancement could look like on this Terraform module. + render: hcl + - type: textarea + id: references + attributes: + label: References + description: | + Information about referencing Github Issues: https://help.github.com/articles/basic-writing-and-formatting-syntax/#referencing-issues-and-pull-requests + + Are there any other GitHub issues (open or closed) or pull requests that should be linked here? Vendor blog posts or documentation? For example: + + * https://azure.microsoft.com/en-us/roadmap/virtual-network-service-endpoint-for-azure-cosmos-db/ \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 8c7b4bd..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve ---- - - - - - - -- Terraform Version: -- OS Version: - -Bug description: - -Steps to reproduce: - -1. -2. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..ec4bb38 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index a820760..0000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project - ---- - - - - diff --git a/.github/autoAssignees.yml b/.github/autoAssignees.yml deleted file mode 100644 index 418f471..0000000 --- a/.github/autoAssignees.yml +++ /dev/null @@ -1,27 +0,0 @@ -labelToAuthor: { - "bug": [metacpp] - "cla-already-signed": [] - "cla-not-required": [] - "cla-required": [] - "cla-signed": [] - "design discussion": [] - "duplicate": [] - "enhancement": [] - "help wanted": [] - "invalid": [] - "kitchen": [] - "microsoft/P2": [] - "microsoft/P3": [] - "need review": [] - "not reproduced": [] - "question": [] - "rake": [] - "ready to merge": [] - "review": [] - "size/M": [] - "size/S": [] - "terraform": [] - "testing": [] - "upstream provider": [] - "wontfix": [] -} diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..9e3a2cf --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "gomod" + directory: "/test" + schedule: + interval: "weekly" \ No newline at end of file diff --git a/.github/labeler.yml b/.github/labeler.yml deleted file mode 100644 index c3ebd28..0000000 --- a/.github/labeler.yml +++ /dev/null @@ -1,7 +0,0 @@ -# Number of labels to fetch (optional). Defaults to 20 -numLabels: 40 -# These labels will not be used even if the issue contains them (optional). -# Pass a blank array if no labels are to be excluded. -# excludeLabels: [] -excludeLabels: - - pinned diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..09ff08e --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,13 @@ +## Describe your changes + +## Issue number + +#000 + +## Checklist before requesting a review +- [ ] The pr title can be used to describe what this pr did in `CHANGELOG.md` file +- [ ] I have executed pre-commit on my machine +- [ ] I have passed pr-check on my machine + +Thanks for your cooperation! + diff --git a/.github/workflows/acc-test.yaml b/.github/workflows/acc-test.yaml new file mode 100644 index 0000000..72edebb --- /dev/null +++ b/.github/workflows/acc-test.yaml @@ -0,0 +1,35 @@ +name: E2E Test +on: + pull_request: + types: [ 'opened', 'synchronize' ] + paths: + - '.github/**' + - '**.go' + - '**.tf' + - '.github/workflows/**' + - '**.md' + - '**/go.mod' + +jobs: + acc-tests: + runs-on: [self-hosted, 1ES.Pool=terraform-azurerm-compute] + environment: + name: acctests + steps: + - uses: actions/checkout@v3 + - name: init + run: | + docker run --rm -v $(pwd):/src -w /src mcr.microsoft.com/azterraform:latest make generate + - name: e2e test + run: | + sh scripts/ci-e2e.sh + - name: upload test version snapshots + uses: actions/upload-artifact@v3 + with: + name: TestRecord-${{ github.event.number }} + retention-days: 60 + path: | + examples/**/TestRecord.md.tmp + - name: version-upgrade test + run: | + sh scripts/ci-version-upgrade.sh diff --git a/.github/workflows/breaking-change-detect.yaml b/.github/workflows/breaking-change-detect.yaml new file mode 100644 index 0000000..f95a454 --- /dev/null +++ b/.github/workflows/breaking-change-detect.yaml @@ -0,0 +1,11 @@ +name: 'Comment on PR' + +on: + workflow_run: + workflows: ["Pre Pull Request Check"] + types: + - completed + +jobs: + comment: + uses: Azure/tfmod-scaffold/.github/workflows/breaking-change-detect.yaml@main \ No newline at end of file diff --git a/.github/workflows/pr-check.yaml b/.github/workflows/pr-check.yaml new file mode 100644 index 0000000..07a8dfe --- /dev/null +++ b/.github/workflows/pr-check.yaml @@ -0,0 +1,15 @@ +name: Pre Pull Request Check +on: + pull_request: + types: ['opened', 'synchronize'] + paths: + - '.github/**' + - '**.go' + - '**.tf' + - '.github/workflows/**' + - '**.md' + - '**/go.mod' + +jobs: + prepr-check: + uses: Azure/tfmod-scaffold/.github/workflows/pr-check.yaml@main \ No newline at end of file diff --git a/.github/workflows/pr-merged.yaml b/.github/workflows/pr-merged.yaml new file mode 100644 index 0000000..6ccf0e6 --- /dev/null +++ b/.github/workflows/pr-merged.yaml @@ -0,0 +1,10 @@ +name: Pr Merged +on: + push: + branches: + - main + - master + +jobs: + pr-merged: + uses: Azure/tfmod-scaffold/.github/workflows/pr-merged.yaml@main \ No newline at end of file diff --git a/.github/workflows/update-changelog.yaml b/.github/workflows/update-changelog.yaml new file mode 100644 index 0000000..43e62a7 --- /dev/null +++ b/.github/workflows/update-changelog.yaml @@ -0,0 +1,12 @@ +name: Update Changelog +on: + push: + branches: + - main + - master + tags: + - '*' + +jobs: + update-changelog: + uses: Azure/tfmod-scaffold/.github/workflows/update-changelog.yaml@main \ No newline at end of file diff --git a/.github/workflows/weekly-e2e.yaml b/.github/workflows/weekly-e2e.yaml new file mode 100644 index 0000000..2e3eced --- /dev/null +++ b/.github/workflows/weekly-e2e.yaml @@ -0,0 +1,32 @@ +name: Weekly E2E Test Check +on: + workflow_dispatch: + schedule: + - cron: '0 0 * * 0' + +jobs: + full-e2e-check: + runs-on: [self-hosted, 1ES.Pool=terraform-azurerm-compute] + environment: + name: crontests + steps: + - name: checkout + uses: actions/checkout@v3 + - name: e2e test + continue-on-error: true + run: | + sh scripts/ci-e2e.sh + - name: Update + run: | + sh scripts/ci-update-test-record.sh + - name: Fix Permission + run: | + cd .git + sudo chmod -R a+rwX . + sudo find . -type d -exec chmod g+s '{}' + + - name: Commit & Push changes + uses: actions-js/push@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + message: 'Update TestVersionSnapshot' + branch: ${{ env.default_branch }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 973a151..2b0fd22 100644 --- a/.gitignore +++ b/.gitignore @@ -1,37 +1,68 @@ -# Variable files -terraform.tfvars - -### https://raw.github.com/github/gitignore/abad92dac5a4306f72242dae3bca6e277bce3615/Terraform.gitignore - -# Compiled files -*.tfstate -*.tfstate.backup - -# Go vendor directory -vendor/ - -# Module directory -.terraform/ -.test-data/ - -### https://raw.github.com/github/gitignore/abad92dac5a4306f72242dae3bca6e277bce3615/Global/Vim.gitignore - -# swap -[._]*.s[a-w][a-z] -[._]s[a-w][a-z] -# session -Session.vim -# temporary -.netrwhist -*~ -# auto-generated tag files -tags - -# IDE configs -.idea - -# Ruby download package lock file. -Gemfile.lock - -# Test Kitchen files. -.kitchen +# Variable files +terraform.tfvars + +### https://raw.github.com/github/gitignore/abad92dac5a4306f72242dae3bca6e277bce3615/Terraform.gitignore + +# Compiled files +*.tfstate +*.tfstate.backup +*.tfvars + +**/.terraform.lock.hcl + +# Terraform directory +.terraform/ +terraform.tfstate.d/ +logs/ + +# Go vendor directory +vendor/ + +# Files generated by terratest +.test-data/ + +# Terraform log file +terraform.log + +### https://raw.github.com/github/gitignore/abad92dac5a4306f72242dae3bca6e277bce3615/Global/Vim.gitignore + +# swap +[._]*.s[a-w][a-z] +[._]s[a-w][a-z] +# session +Session.vim +# temporary +.netrwhist +*~ +# auto-generated tag files +tags + +# IDE configs +.idea + +# Ruby download package lock file. +Gemfile.lock + +# Mac folder attribute file +.DS_Store + +.terraform.tfstate.lock.info + +# SSH Key +private_ssh_key + +# generated readme by the pr-check job + +README-generated.md + +**/override.tf + +.tflint.hcl +.tflint_example.hcl + +tfmod-scaffold/ +scripts +test/go.sum + +/TestRecord +**/TestRecord.md.tmp \ No newline at end of file diff --git a/.tflint_alt.hcl b/.tflint_alt.hcl new file mode 100644 index 0000000..f14b4ff --- /dev/null +++ b/.tflint_alt.hcl @@ -0,0 +1,257 @@ +/* +THIS FILE IS GENERATED BY TFMOD-SCAFFOLD, PLEASE DO NOT MODIFY IT. +IF YOU WANT TO USE A CUSTOMIZED CONFIGURATION, PLEASE CREATE YOUR OWN AND +SET THIS FILE'S PATH TO $TFLINT_CONFIG ENVVIRONMENT VARIABLE. +*/ + +plugin "azurerm" { + enabled = true + version = "0.20.0" + source = "github.com/terraform-linters/tflint-ruleset-azurerm" +} + +plugin "basic-ext" { + enabled = true + version = "0.3.1" + source = "github.com/Azure/tflint-ruleset-basic-ext" + signing_key = <<-KEY +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBGNjIIoBEACni6mzvCfY14cicqnW+BjFCoTUM95nxUINDFEQ7wkxGWmufAvQ +iEUDrv6iVNCEfk1cU2jGSWUlBu6hTZ9auOy8K2MrMhtdqYVx+mY1SS+fVYHzSQAC +C3qBTBY+TmDHl0QMQjF884AsYE2WTcZI3e1DOXXkVKlOMERzT7IQMVbeuiVklwLj +BA/sQISaZVesaWPWN8WtRb8iOrq4i5HHqnAWRZGtqMEsoNBWqzjqh1aoQ7Ex3ldH +2Ey1bEIi05PWr67k1QOU9pXhMNuC+NXCQDO1sEq/NG376v2GbgylVapUlWAq35tw +Ut8SFfiDM+GyHN1nNNjBKhOB7774yqh6FrPIfh/2WvN1EhAbPkr9eWfHROyIPWj1 +t+IBFlMFbvMHLeMrlSZAkqlLljEZHdfzBfEXGUYKOOz/aeR+XjeMxGX977VoMk/0 +uzLQPoVMqjOrAY4Iq+XhW6w4aBihDqkot3TDH6Cyczl+N9We0QatWd5jAG+BTb22 +7AevzSlDKh/+oUAec6iG/WF4MjJB3c1Fdpkw4rtTjha6zKrFHNvpDzuyvJEnO9Pt +eBRAWaQvkqfMccQMYsasHWYkZKH2U8RAsqgW8iF9aRktBdGPao+ztkblbj/c7dUz +L4J28SmivzDJAzoAANjiC2R6xLBOb6b+TyafFmgevepwgN1QG5bPY3MptwARAQAB +tB9oZXppamllIDxoZXppamllQG1pY3Jvc29mdC5jb20+iQJOBBMBCgA4FiEEE0LC +37JNq10/9GosWbcR7NOMOjwFAmNjIIoCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgEC +F4AACgkQWbcR7NOMOjxtdQ//TUh2/GmF+4TF2qLgRgHKaH8pL8cUaCgYdUNlrK7B +8OXBKIxrnNs8FXUSsgyKHMjAdg0EdSgJt+w9nOgZEtLPTOE+e3RKgmmsMc9vn/qq +qoOw8B6NxRIJsGp8YbemoDUnmrUK93TSRxINBup4y413ZoON7g8O7I8kQBz4Ra6E +6U+Yx5rstFeS5D5jzWYeoh9Y6g9zucEDe3qnS8LcPmhj95mrm7A4uNwMDmny/J7B +I3sVILAybp8D8/PSSixjGsCr+81marKlkHxqaSL8dpR2tr2Z1lcm2gS4z81NXlx4 +vh79cvpX8hedysssl8FpV3SzxYFlgWNP97vM9AAv00fBOR4lid3ZNoRCQdfw7LeT +GrglCWmMZ3Pm8JClYTbcsQ5wg5JgPFU9Rht+QN/EoNfJ5RipYYYwE9AOSJ6eJxHB +QH9pM1b+dZ6dYLqReeGUlZ0pYBoLC+LpqknxlPQzUuPl5VbbL2TsFIVy5n963gAk +5vEnRJgUFx+agI6ZPw+SnXRjwgqvuasgE8Z6wwbXWnSZf1kbJr4sv5alN/u1Uyph +CYl3uuHJkm0D/YfH4b83Bq7saTXWmJib7AR4piB8Z81vpO+Nq3zcvX1Z3r0AlF4j +t0KDU/cix305ldEITT7EJAxkxI71XCTgdt78h/e2N1gLatsv8I98ShK/U6Jxb0kx +pLC5Ag0EY2MgigEQAJJgnoe58UiuSFJIxPY6g4djYrWm7R9gw8oCdWJhjT9ou+bD +HYIY0RaaXuUsBaA/logdO87MeiIyPirypPhpSHN1c6CXBfLyspO606su8AKS+DK3 +lTzExtU8c5lwP0KnDDugs/qbjpntrXCCUmxTF2RDMFbkbaAt9vl671+kggXvOfe/ +iJFXjWXfBx/nKeMkHmXo6qpizurqe0CYdlOW2w7UXjeX8snuOz7kFK3PhEHJ8CKA +UEwqQaEp8v5zbAWGzRzPbY3Djw1RHw/WT6gEZWPQYK0HP6VdwIVJhpp8RKUe3QHJ +cG/hUJrEdbLOZrBe5NZCP5RStJ3XL4aAVS0nu/18nB1vf7pYq6VaywEM9n5PuLWr +mdtvUMTaDLjLM9H24qU8wHbiy+3jMGIUz5sKKIkBN8VxGacHo7Aadk7npGwiLpPD +VV0L5eapSCgf1Nja7ZDnzgzlcztg7eBV7r+tRBsgtWiFlDu00NZCowGfxeaWc7TZ +08JweBe4VDpUZZLiA/J2ET0/qAfDtTLtLbMrcgFuIZi0f05FG0qtW5SuVVuYGfdE +F7rUYFC5F39GxiDElR9F4XQcfhhtzAwVe9cYquPEkFBovzwhcVyJ3sfvupbk2nTN +koBjcs0n5C1b3YiaYeGM06hAXD0OTnl0Pbx1qMXTNs3DLCUoraU6tAwSvU4LABEB +AAGJAjYEGAEKACAWIQQTQsLfsk2rXT/0aixZtxHs04w6PAUCY2MgigIbDAAKCRBZ +txHs04w6POmfD/9GJ5sxWnwv8wzU46K4pK/Ie6AVCVIPgtqGIvifHwz4VM9VGIyb +oFTlRjow+i1z/8hb3tqdaJZvHkAv6jTPX6N3UiZ9l81LOqBJsx+vBHOSKAIRlgqX +jZ97N5y2H62BmBLqJxqA+C/8JhgrTiNB6pNAwet2mBgXCt2GDgy9UVgJ0Y/wJ2lk +E5LZOilxqd7P+qCruaCPyjyNkMTU9b3C2qR46Ip1GWc//UWwmLKCYsF+eVUst9Mk +O4QVJTj1B51mCXgrhg0ei8lNzXHw79W2MpEG6+HRUzyJqGylxh8B4BKwvGEr6PkC +QN8QE7kGhxLNXPNjAyM15lWOckR0nPkwV5zV+gpw+R5grOgnBcMIhoMkUKiFqnbd +km5bxwF00OL/QqocAvOUY44G1WtsigAeNu3OM3ki1j6VVAOlwljQ8OSdLuVM3vsU +Q2i0lo99PuDaAjTxCFPx7+/TsL5vL21zGvVpkWvXsfVLFvjo2bTs5Yc78MGF4IZN +o4QUqU7MGkjT7r8rFSPwFkAny0vUkp5iAKKaQFSvi5j1SNExtSeWk+cfjHwrH9l5 +U6WDcghw5dibCpCUg5Eh0pbVe/Wdql3Y63Urk35fFAtGGpHozoVpoWFg6+n5HVlo +1DSrn+zuuxMp02sV+9MfqnT8Gq3fbU1mlTmqALKWa71w1dAv/M1kdjgA5w== +=nfI3 +-----END PGP PUBLIC KEY BLOCK----- + KEY +} + +plugin "azurerm-ext" { + enabled = true + version = "0.2.0" + source = "github.com/Azure/tflint-ruleset-azurerm-ext" + signing_key = <<-KEY +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBGNjIIoBEACni6mzvCfY14cicqnW+BjFCoTUM95nxUINDFEQ7wkxGWmufAvQ +iEUDrv6iVNCEfk1cU2jGSWUlBu6hTZ9auOy8K2MrMhtdqYVx+mY1SS+fVYHzSQAC +C3qBTBY+TmDHl0QMQjF884AsYE2WTcZI3e1DOXXkVKlOMERzT7IQMVbeuiVklwLj +BA/sQISaZVesaWPWN8WtRb8iOrq4i5HHqnAWRZGtqMEsoNBWqzjqh1aoQ7Ex3ldH +2Ey1bEIi05PWr67k1QOU9pXhMNuC+NXCQDO1sEq/NG376v2GbgylVapUlWAq35tw +Ut8SFfiDM+GyHN1nNNjBKhOB7774yqh6FrPIfh/2WvN1EhAbPkr9eWfHROyIPWj1 +t+IBFlMFbvMHLeMrlSZAkqlLljEZHdfzBfEXGUYKOOz/aeR+XjeMxGX977VoMk/0 +uzLQPoVMqjOrAY4Iq+XhW6w4aBihDqkot3TDH6Cyczl+N9We0QatWd5jAG+BTb22 +7AevzSlDKh/+oUAec6iG/WF4MjJB3c1Fdpkw4rtTjha6zKrFHNvpDzuyvJEnO9Pt +eBRAWaQvkqfMccQMYsasHWYkZKH2U8RAsqgW8iF9aRktBdGPao+ztkblbj/c7dUz +L4J28SmivzDJAzoAANjiC2R6xLBOb6b+TyafFmgevepwgN1QG5bPY3MptwARAQAB +tB9oZXppamllIDxoZXppamllQG1pY3Jvc29mdC5jb20+iQJOBBMBCgA4FiEEE0LC +37JNq10/9GosWbcR7NOMOjwFAmNjIIoCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgEC +F4AACgkQWbcR7NOMOjxtdQ//TUh2/GmF+4TF2qLgRgHKaH8pL8cUaCgYdUNlrK7B +8OXBKIxrnNs8FXUSsgyKHMjAdg0EdSgJt+w9nOgZEtLPTOE+e3RKgmmsMc9vn/qq +qoOw8B6NxRIJsGp8YbemoDUnmrUK93TSRxINBup4y413ZoON7g8O7I8kQBz4Ra6E +6U+Yx5rstFeS5D5jzWYeoh9Y6g9zucEDe3qnS8LcPmhj95mrm7A4uNwMDmny/J7B +I3sVILAybp8D8/PSSixjGsCr+81marKlkHxqaSL8dpR2tr2Z1lcm2gS4z81NXlx4 +vh79cvpX8hedysssl8FpV3SzxYFlgWNP97vM9AAv00fBOR4lid3ZNoRCQdfw7LeT +GrglCWmMZ3Pm8JClYTbcsQ5wg5JgPFU9Rht+QN/EoNfJ5RipYYYwE9AOSJ6eJxHB +QH9pM1b+dZ6dYLqReeGUlZ0pYBoLC+LpqknxlPQzUuPl5VbbL2TsFIVy5n963gAk +5vEnRJgUFx+agI6ZPw+SnXRjwgqvuasgE8Z6wwbXWnSZf1kbJr4sv5alN/u1Uyph +CYl3uuHJkm0D/YfH4b83Bq7saTXWmJib7AR4piB8Z81vpO+Nq3zcvX1Z3r0AlF4j +t0KDU/cix305ldEITT7EJAxkxI71XCTgdt78h/e2N1gLatsv8I98ShK/U6Jxb0kx +pLC5Ag0EY2MgigEQAJJgnoe58UiuSFJIxPY6g4djYrWm7R9gw8oCdWJhjT9ou+bD +HYIY0RaaXuUsBaA/logdO87MeiIyPirypPhpSHN1c6CXBfLyspO606su8AKS+DK3 +lTzExtU8c5lwP0KnDDugs/qbjpntrXCCUmxTF2RDMFbkbaAt9vl671+kggXvOfe/ +iJFXjWXfBx/nKeMkHmXo6qpizurqe0CYdlOW2w7UXjeX8snuOz7kFK3PhEHJ8CKA +UEwqQaEp8v5zbAWGzRzPbY3Djw1RHw/WT6gEZWPQYK0HP6VdwIVJhpp8RKUe3QHJ +cG/hUJrEdbLOZrBe5NZCP5RStJ3XL4aAVS0nu/18nB1vf7pYq6VaywEM9n5PuLWr +mdtvUMTaDLjLM9H24qU8wHbiy+3jMGIUz5sKKIkBN8VxGacHo7Aadk7npGwiLpPD +VV0L5eapSCgf1Nja7ZDnzgzlcztg7eBV7r+tRBsgtWiFlDu00NZCowGfxeaWc7TZ +08JweBe4VDpUZZLiA/J2ET0/qAfDtTLtLbMrcgFuIZi0f05FG0qtW5SuVVuYGfdE +F7rUYFC5F39GxiDElR9F4XQcfhhtzAwVe9cYquPEkFBovzwhcVyJ3sfvupbk2nTN +koBjcs0n5C1b3YiaYeGM06hAXD0OTnl0Pbx1qMXTNs3DLCUoraU6tAwSvU4LABEB +AAGJAjYEGAEKACAWIQQTQsLfsk2rXT/0aixZtxHs04w6PAUCY2MgigIbDAAKCRBZ +txHs04w6POmfD/9GJ5sxWnwv8wzU46K4pK/Ie6AVCVIPgtqGIvifHwz4VM9VGIyb +oFTlRjow+i1z/8hb3tqdaJZvHkAv6jTPX6N3UiZ9l81LOqBJsx+vBHOSKAIRlgqX +jZ97N5y2H62BmBLqJxqA+C/8JhgrTiNB6pNAwet2mBgXCt2GDgy9UVgJ0Y/wJ2lk +E5LZOilxqd7P+qCruaCPyjyNkMTU9b3C2qR46Ip1GWc//UWwmLKCYsF+eVUst9Mk +O4QVJTj1B51mCXgrhg0ei8lNzXHw79W2MpEG6+HRUzyJqGylxh8B4BKwvGEr6PkC +QN8QE7kGhxLNXPNjAyM15lWOckR0nPkwV5zV+gpw+R5grOgnBcMIhoMkUKiFqnbd +km5bxwF00OL/QqocAvOUY44G1WtsigAeNu3OM3ki1j6VVAOlwljQ8OSdLuVM3vsU +Q2i0lo99PuDaAjTxCFPx7+/TsL5vL21zGvVpkWvXsfVLFvjo2bTs5Yc78MGF4IZN +o4QUqU7MGkjT7r8rFSPwFkAny0vUkp5iAKKaQFSvi5j1SNExtSeWk+cfjHwrH9l5 +U6WDcghw5dibCpCUg5Eh0pbVe/Wdql3Y63Urk35fFAtGGpHozoVpoWFg6+n5HVlo +1DSrn+zuuxMp02sV+9MfqnT8Gq3fbU1mlTmqALKWa71w1dAv/M1kdjgA5w== +=nfI3 +-----END PGP PUBLIC KEY BLOCK----- + KEY +} + +rule "terraform_comment_syntax" { + enabled = true +} + +rule "terraform_deprecated_index" { + enabled = true +} + +rule "terraform_deprecated_interpolation" { + enabled = true +} + +rule "terraform_documented_outputs" { + enabled = true +} + +rule "terraform_documented_variables" { + enabled = true +} + +rule "terraform_empty_list_equality" { + enabled = true +} + +rule "terraform_module_pinned_source" { + enabled = true +} + +rule "terraform_module_version" { + enabled = true +} + +rule "terraform_naming_convention" { + enabled = true +} + +rule "terraform_required_providers" { + enabled = true +} + +rule "terraform_required_version" { + enabled = true +} + +rule "terraform_standard_module_structure" { + enabled = true +} + +rule "terraform_typed_variables" { + enabled = true +} + +rule "terraform_unused_declarations" { + enabled = true +} + +rule "terraform_unused_required_providers" { + enabled = true +} + +rule "terraform_workspace_remote" { + enabled = true +} + +rule "terraform_locals_order" { + enabled = true +} + +rule "terraform_output_order" { + enabled = true +} + +rule "terraform_output_separate" { + enabled = true +} + +rule "terraform_variable_nullable_false" { + enabled = true +} + +rule "terraform_variable_order" { + enabled = true +} + +rule "terraform_variable_separate" { + enabled = true +} + +rule "terraform_resource_data_arg_layout" { + enabled = true +} + +rule "azurerm_arg_order" { + enabled = true +} + +rule "azurerm_resource_tag" { + enabled = true +} + +rule "terraform_count_index_usage" { + enabled = false +} + +rule "terraform_heredoc_usage" { + enabled = true +} + +rule "terraform_module_provider_declaration" { + enabled = true +} + +rule "terraform_required_providers_declaration" { + enabled = true +} + +rule "terraform_required_version_declaration" { + enabled = true +} + +rule "terraform_sensitive_variable_no_default" { + enabled = true +} + +rule "terraform_versions_file" { + enabled = true +} \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a82d22b..0000000 --- a/.travis.yml +++ /dev/null @@ -1,31 +0,0 @@ -# Build matrix / environment variable are explained on: -# http://about.travis-ci.org/docs/user/build-configuration/ -# This file can be validated on: -# http://lint.travis-ci.org/ -sudo: required - -language: ruby - -rvm: - - 2.3 - -services: - - docker - -env: - - TERRAFORM_VERSION=0.13.0 IMAGE_NAME=azure-compute-module - -jobs: - include: - - stage: build - if: type = pull_request - install: true - script: - - docker build --build-arg BUILD_TERRAFORM_VERSION=${TERRAFORM_VERSION} -t ${IMAGE_NAME} . - - docker run ${IMAGE_NAME} rake build - - stage: full - if: type = push AND branch = master - install: true - script: - - docker build --build-arg BUILD_TERRAFORM_VERSION=${TERRAFORM_VERSION} --build-arg BUILD_ARM_SUBSCRIPTION_ID=$ARM_SUBSCRIPTION_ID --build-arg BUILD_ARM_CLIENT_ID=$ARM_CLIENT_ID --build-arg BUILD_ARM_CLIENT_SECRET=$ARM_CLIENT_SECRET --build-arg BUILD_ARM_TENANT_ID=$ARM_TENANT_ID -t ${IMAGE_NAME} . - - docker run ${IMAGE_NAME} rake full diff --git a/CHANGELOG-v3.md b/CHANGELOG-v3.md new file mode 100644 index 0000000..d61e7f9 --- /dev/null +++ b/CHANGELOG-v3.md @@ -0,0 +1,7 @@ +# 3.14.0 (May 25, 2021) + +ENHANCEMENTS: + +* Add variable `delete_data_disks_on_termination`. [#170](https://github.com/Azure/terraform-azurerm-compute/pull/170) + +BUG FIXES: diff --git a/CHANGELOG-v4.md b/CHANGELOG-v4.md new file mode 100644 index 0000000..7c63b9e --- /dev/null +++ b/CHANGELOG-v4.md @@ -0,0 +1,41 @@ +# Changelog + +## [4.2.0](https://github.com/Azure/terraform-azurerm-compute/tree/4.2.0) (2022-12-22) + +**Merged pull requests:** + +- Changed var.location default value from "" to null [\#214](https://github.com/Azure/terraform-azurerm-compute/pull/214) ([juanjojulian](https://github.com/juanjojulian)) +- Bump tflint plugin version, add new checking rule [\#213](https://github.com/Azure/terraform-azurerm-compute/pull/213) ([lonegunmanb](https://github.com/lonegunmanb)) +- Upgrade `terraform-module-test-helper` lib so we can get rid of override file to execute version upgrade test [\#212](https://github.com/Azure/terraform-azurerm-compute/pull/212) ([lonegunmanb](https://github.com/lonegunmanb)) + +## [4.1.0](https://github.com/Azure/terraform-azurerm-compute/tree/4.1.0) (2022-11-29) + +**Merged pull requests:** + +- Add support for `azurerm_virtual_machine_extension` resource. [\#210](https://github.com/Azure/terraform-azurerm-compute/pull/210) ([lonegunmanb](https://github.com/lonegunmanb)) +- Add support for `plan` block [\#209](https://github.com/Azure/terraform-azurerm-compute/pull/209) ([lonegunmanb](https://github.com/lonegunmanb)) +- Support boot diagnostics storage injection [\#208](https://github.com/Azure/terraform-azurerm-compute/pull/208) ([lonegunmanb](https://github.com/lonegunmanb)) +- Add support for network security group injection [\#205](https://github.com/Azure/terraform-azurerm-compute/pull/205) ([lonegunmanb](https://github.com/lonegunmanb)) +- Solve \#107 by adding `create_before_destroy = true` to defer public ip's deletion. [\#203](https://github.com/Azure/terraform-azurerm-compute/pull/203) ([lonegunmanb](https://github.com/lonegunmanb)) +- Output vm names [\#202](https://github.com/Azure/terraform-azurerm-compute/pull/202) ([lonegunmanb](https://github.com/lonegunmanb)) +- Make `azurerm_availability_set`'s `platform_fault_domain_count` and `platform_update_domain_count` configurable [\#201](https://github.com/Azure/terraform-azurerm-compute/pull/201) ([lonegunmanb](https://github.com/lonegunmanb)) +- Fix the bug introduced by \#189 by adding `var.storage_os_disk_size_gb` back. [\#200](https://github.com/Azure/terraform-azurerm-compute/pull/200) ([lonegunmanb](https://github.com/lonegunmanb)) +- Add support for `azurerm_virtual_machine.zones` [\#199](https://github.com/Azure/terraform-azurerm-compute/pull/199) ([lonegunmanb](https://github.com/lonegunmanb)) +- Convert legacy splat syntax into bracket syntax [\#198](https://github.com/Azure/terraform-azurerm-compute/pull/198) ([lonegunmanb](https://github.com/lonegunmanb)) +- Replacing location and resource\_group\_name with local variables [\#196](https://github.com/Azure/terraform-azurerm-compute/pull/196) ([jiaweitao001](https://github.com/jiaweitao001)) +- Removing depends\_on in examples code [\#195](https://github.com/Azure/terraform-azurerm-compute/pull/195) ([jiaweitao001](https://github.com/jiaweitao001)) +- Add override template file so we can run version upgrade test [\#194](https://github.com/Azure/terraform-azurerm-compute/pull/194) ([lonegunmanb](https://github.com/lonegunmanb)) +- Rename some resources to snake case. [\#193](https://github.com/Azure/terraform-azurerm-compute/pull/193) ([lonegunmanb](https://github.com/lonegunmanb)) +- Refactor code to meet tflint's requirements [\#189](https://github.com/Azure/terraform-azurerm-compute/pull/189) ([lonegunmanb](https://github.com/lonegunmanb)) +- Allow custom OS disk size [\#183](https://github.com/Azure/terraform-azurerm-compute/pull/183) ([yatzek](https://github.com/yatzek)) + +## [4.0.0](https://github.com/Azure/terraform-azurerm-compute/tree/4.0.0) (2022-11-15) + +**Merged pull requests:** + +- Add CI pipeline [\#185](https://github.com/Azure/terraform-azurerm-compute/pull/185) ([jiaweitao001](https://github.com/jiaweitao001)) +- Adding Microsoft SECURITY.MD [\#181](https://github.com/Azure/terraform-azurerm-compute/pull/181) ([microsoft-github-policy-service[bot]](https://github.com/apps/microsoft-github-policy-service)) + + + +\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..97e48f5 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,35 @@ +# Changelog + +## [Unreleased](https://github.com/Azure/terraform-azurerm-compute/tree/HEAD) + +**Merged pull requests:** + +- Use name template variables to customize the name of each resource [\#234](https://github.com/Azure/terraform-azurerm-compute/pull/234) ([DatsloJRel](https://github.com/DatsloJRel)) +- Improve separate data disk resource logic [\#233](https://github.com/Azure/terraform-azurerm-compute/pull/233) ([lonegunmanb](https://github.com/lonegunmanb)) +- Pin the iterator variable to avoid concurrent test error [\#232](https://github.com/Azure/terraform-azurerm-compute/pull/232) ([lonegunmanb](https://github.com/lonegunmanb)) +- Bump github.com/gruntwork-io/terratest from 0.41.9 to 0.41.10 in /test [\#231](https://github.com/Azure/terraform-azurerm-compute/pull/231) ([dependabot[bot]](https://github.com/apps/dependabot)) +- Bump github.com/Azure/terraform-module-test-helper from 0.7.1 to 0.8.1 in /test [\#230](https://github.com/Azure/terraform-azurerm-compute/pull/230) ([dependabot[bot]](https://github.com/apps/dependabot)) +- Create data disks and attach after VM creation by azurerm\_virtual\_machine\_data\_disk\_attachment resource. [\#227](https://github.com/Azure/terraform-azurerm-compute/pull/227) ([DatsloJRel](https://github.com/DatsloJRel)) + +## [5.1.0](https://github.com/Azure/terraform-azurerm-compute/tree/5.1.0) (2023-02-06) + +**Merged pull requests:** + +- Optionally create an availability set [\#228](https://github.com/Azure/terraform-azurerm-compute/pull/228) ([DatsloJRel](https://github.com/DatsloJRel)) +- Update `README.md` with Powershell error resolution. [\#226](https://github.com/Azure/terraform-azurerm-compute/pull/226) ([DatsloJRel](https://github.com/DatsloJRel)) +- Update readme to recommend new virtual machine module [\#225](https://github.com/Azure/terraform-azurerm-compute/pull/225) ([lonegunmanb](https://github.com/lonegunmanb)) +- Bump github.com/Azure/terraform-module-test-helper from 0.6.0 to 0.7.1 in /test [\#221](https://github.com/Azure/terraform-azurerm-compute/pull/221) ([dependabot[bot]](https://github.com/apps/dependabot)) +- Bump github.com/gruntwork-io/terratest from 0.41.7 to 0.41.9 in /test [\#219](https://github.com/Azure/terraform-azurerm-compute/pull/219) ([dependabot[bot]](https://github.com/apps/dependabot)) + +## [5.0.0](https://github.com/Azure/terraform-azurerm-compute/tree/5.0.0) (2023-01-16) + +**Merged pull requests:** + +- Adjust the "plan" input to properly set the image name and product [\#218](https://github.com/Azure/terraform-azurerm-compute/pull/218) ([AWSmith0216](https://github.com/AWSmith0216)) +- Add support for multiple virtual machine extensions. [\#217](https://github.com/Azure/terraform-azurerm-compute/pull/217) ([lonegunmanb](https://github.com/lonegunmanb)) +- Bump github.com/Azure/terraform-module-test-helper from 0.4.0 to 0.6.0 in /test [\#216](https://github.com/Azure/terraform-azurerm-compute/pull/216) ([dependabot[bot]](https://github.com/apps/dependabot)) +- Bump github.com/gruntwork-io/terratest from 0.41.6 to 0.41.7 in /test [\#215](https://github.com/Azure/terraform-azurerm-compute/pull/215) ([dependabot[bot]](https://github.com/apps/dependabot)) + + + +\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 4e8f8e1..0000000 --- a/Dockerfile +++ /dev/null @@ -1,37 +0,0 @@ -# Pull the base image with given version. -ARG BUILD_TERRAFORM_VERSION="0.13.0" -FROM mcr.microsoft.com/terraform-test:${BUILD_TERRAFORM_VERSION} - -ARG MODULE_NAME="terraform-azurerm-compute" - -# Declare default build configurations for terraform. -ARG BUILD_ARM_SUBSCRIPTION_ID="" -ARG BUILD_ARM_CLIENT_ID="" -ARG BUILD_ARM_CLIENT_SECRET="" -ARG BUILD_ARM_TENANT_ID="" -ARG BUILD_ARM_TEST_LOCATION="WestEurope" -ARG BUILD_ARM_TEST_LOCATION_ALT="WestUS" - -# Set environment variables for terraform runtime. -ENV ARM_SUBSCRIPTION_ID=${BUILD_ARM_SUBSCRIPTION_ID} -ENV ARM_CLIENT_ID=${BUILD_ARM_CLIENT_ID} -ENV ARM_CLIENT_SECRET=${BUILD_ARM_CLIENT_SECRET} -ENV ARM_TENANT_ID=${BUILD_ARM_TENANT_ID} -ENV ARM_TEST_LOCATION=${BUILD_ARM_TEST_LOCATION} -ENV ARM_TEST_LOCATION_ALT=${BUILD_ARM_TEST_LOCATION_ALT} - -# Set work directory and generate ssh key -RUN mkdir /go -RUN mkdir /go/bin -RUN mkdir /go/src -RUN mkdir /go/src/${MODULE_NAME} -COPY . /go/src/${MODULE_NAME} -WORKDIR /go/src/${MODULE_NAME} -RUN ssh-keygen -q -t rsa -b 4096 -f $HOME/.ssh/id_rsa - -# Install required go packages using dep ensure -ENV GOPATH /go -ENV PATH /usr/local/go/bin:$GOPATH/bin:$PATH -RUN /bin/bash -c "curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh" - -RUN ["bundle", "install", "--gemfile", "./Gemfile"] diff --git a/GNUmakefile b/GNUmakefile new file mode 100644 index 0000000..8e9f0bf --- /dev/null +++ b/GNUmakefile @@ -0,0 +1,9 @@ +SHELL := /bin/bash + +-include $(shell curl -sSL "https://raw.githubusercontent.com/Azure/tfmod-scaffold/main/scripts/install.sh" | bash -s > /dev/null ; echo tfmod-scaffold/GNUmakefile) + +init: + @sh "$(CURDIR)/scripts/init.sh" + +cleanup: + @sh "$(CURDIR)/scripts/cleanup.sh" \ No newline at end of file diff --git a/Gemfile b/Gemfile deleted file mode 100644 index a5a775a..0000000 --- a/Gemfile +++ /dev/null @@ -1,7 +0,0 @@ -source 'https://rubygems.org/' - -group :test do - git 'https://github.com/Azure/terramodtest.git' do - gem 'terramodtest', tag: '0.7.0' - end -end diff --git a/Gopkg.lock b/Gopkg.lock deleted file mode 100644 index 4328b02..0000000 --- a/Gopkg.lock +++ /dev/null @@ -1,158 +0,0 @@ -# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. - - -[[projects]] - digest = "1:c3948295121c6b6f3db7c97733f1eb33f48f5d526a36e083434deb91d2e2cbeb" - name = "github.com/aws/aws-sdk-go" - packages = [ - "aws", - "aws/awserr", - "aws/awsutil", - "aws/client", - "aws/client/metadata", - "aws/corehandlers", - "aws/credentials", - "aws/credentials/ec2rolecreds", - "aws/credentials/endpointcreds", - "aws/credentials/stscreds", - "aws/csm", - "aws/defaults", - "aws/ec2metadata", - "aws/endpoints", - "aws/request", - "aws/session", - "aws/signer/v4", - "internal/sdkio", - "internal/sdkrand", - "internal/sdkuri", - "internal/shareddefaults", - "private/protocol", - "private/protocol/ec2query", - "private/protocol/eventstream", - "private/protocol/eventstream/eventstreamapi", - "private/protocol/json/jsonutil", - "private/protocol/jsonrpc", - "private/protocol/query", - "private/protocol/query/queryutil", - "private/protocol/rest", - "private/protocol/restxml", - "private/protocol/xml/xmlutil", - "service/acm", - "service/autoscaling", - "service/cloudwatchlogs", - "service/ec2", - "service/iam", - "service/kms", - "service/s3", - "service/s3/s3iface", - "service/s3/s3manager", - "service/sns", - "service/sqs", - "service/sts", - ] - pruneopts = "UT" - revision = "96313df4b1ba47ade3de7b2b90d69c02a81ddaa4" - version = "v1.14.33" - -[[projects]] - digest = "1:e625e9cdf85de9b3cfd4324c4575d6dd23f9ac1e63f21f82c39d719d8698e79e" - name = "github.com/boombuler/barcode" - packages = [ - ".", - "qr", - "utils", - ] - pruneopts = "UT" - revision = "3cfea5ab600ae37946be2b763b8ec2c1cf2d272d" - version = "v1.0.0" - -[[projects]] - digest = "1:fe8a03a8222d5b913f256972933d26d24ad7c8286692a42943bc01633cc8fce3" - name = "github.com/go-ini/ini" - packages = ["."] - pruneopts = "UT" - revision = "358ee7663966325963d4e8b2e1fbd570c5195153" - version = "v1.38.1" - -[[projects]] - digest = "1:8f8811f9be822914c3a25c6a071e93beb4c805d7b026cbf298bc577bc1cc945b" - name = "github.com/google/uuid" - packages = ["."] - pruneopts = "UT" - revision = "064e2069ce9c359c118179501254f67d7d37ba24" - version = "0.2" - -[[projects]] - digest = "1:a2516c7b3e32d4545ea703e557d81755eff228623dcad3ce1039acc28d400544" - name = "github.com/gruntwork-io/terratest" - packages = [ - "modules/aws", - "modules/collections", - "modules/files", - "modules/logger", - "modules/packer", - "modules/random", - "modules/retry", - "modules/shell", - "modules/ssh", - "modules/terraform", - "modules/test-structure", - ] - pruneopts = "UT" - revision = "a3246fed2e3e40b345e13a745e435b36b17d0c16" - version = "v0.9.15" - -[[projects]] - digest = "1:e22af8c7518e1eab6f2eab2b7d7558927f816262586cd6ed9f349c97a6c285c4" - name = "github.com/jmespath/go-jmespath" - packages = ["."] - pruneopts = "UT" - revision = "0b12d6b5" - -[[projects]] - digest = "1:712648e44349f70516f646f06ab39fd48c311a3cade3ae1f951dc9488150a382" - name = "github.com/pquerna/otp" - packages = [ - ".", - "hotp", - "totp", - ] - pruneopts = "UT" - revision = "b7b89250c468c06871d3837bee02e2d5c155ae19" - version = "v1.0.0" - -[[projects]] - branch = "master" - digest = "1:6126c75359647f32c1437fead4933069915ead6a924cdf9f645a0e6e71262bff" - name = "golang.org/x/crypto" - packages = [ - "curve25519", - "ed25519", - "ed25519/internal/edwards25519", - "internal/chacha20", - "internal/subtle", - "poly1305", - "ssh", - ] - pruneopts = "UT" - revision = "c126467f60eb25f8f27e5a981f32a87e3965053f" - -[[projects]] - branch = "master" - digest = "1:76ee51c3f468493aff39dbacc401e8831fbb765104cbf613b89bef01cf4bad70" - name = "golang.org/x/net" - packages = ["context"] - pruneopts = "UT" - revision = "3673e40ba22529d22c3fd7c93e97b0ce50fa7bdd" - -[solve-meta] - analyzer-name = "dep" - analyzer-version = 1 - input-imports = [ - "github.com/gruntwork-io/terratest/modules/retry", - "github.com/gruntwork-io/terratest/modules/ssh", - "github.com/gruntwork-io/terratest/modules/terraform", - "github.com/gruntwork-io/terratest/modules/test-structure", - ] - solver-name = "gps-cdcl" - solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml deleted file mode 100644 index 9b0dd07..0000000 --- a/Gopkg.toml +++ /dev/null @@ -1,34 +0,0 @@ -# Gopkg.toml example -# -# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html -# for detailed Gopkg.toml documentation. -# -# required = ["github.com/user/thing/cmd/thing"] -# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] -# -# [[constraint]] -# name = "github.com/user/project" -# version = "1.0.0" -# -# [[constraint]] -# name = "github.com/user/project2" -# branch = "dev" -# source = "github.com/myfork/project2" -# -# [[override]] -# name = "github.com/x/y" -# version = "2.4.0" -# -# [prune] -# non-go = false -# go-tests = true -# unused-packages = true - - -[[constraint]] - name = "github.com/gruntwork-io/terratest" - version = "0.9.15" - -[prune] - go-tests = true - unused-packages = true diff --git a/README.md b/README.md index 7990213..882904d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,40 @@ # terraform-azurerm-compute -[![Build Status](https://travis-ci.org/Azure/terraform-azurerm-compute.svg?branch=master)](https://travis-ci.org/Azure/terraform-azurerm-compute) +## Notice on new alternative virtual machine module + +This module was designed and implemented for AzureRM Provider v2.x, It's impossible to refactor this module from `azurerm_virtual_machine` to the modern version `azurerm_linux_virtual_machine` and `azurerm_windows_virtual_machine`. For those who're maintaining infrastructure on brownfield, you're welcome to continue using this module; for those who're about to provision new infrastructure on greenfield, you're welcome to try our new alternative: [terraform-azurerm-virtual-machine](https://registry.terraform.io/modules/Azure/virtual-machine/azurerm/latest). + +## Notice on Upgrade to v5.x + +As [#218](https://github.com/Azure/terraform-azurerm-compute/pull/218) described, the `plan` block introduced by [#209](https://github.com/Azure/terraform-azurerm-compute/pull/209) was incorrect so we must adjust the assignments' order, which is a breaking change. The change we've made to vm's `plan` block is: + +```hcl +dynamic "plan" { + for_each = var.is_marketplace_image ? ["plan"] : [] + + content { + - name = var.vm_os_offer + - product = var.vm_os_sku + + name = var.vm_os_sku + + product = var.vm_os_offer + publisher = var.vm_os_publisher + } +} +``` + +Now `vm_os_sku` would be used as `plan.name` and `vm_os_offer` would be used as `plan.product`. + +v5.0.0 is a major version upgrade. Extreme caution must be taken during the upgrade to avoid resource replacement and downtime by accident. + +## Notice on Upgrade to v4.x + +We've added a CI pipeline for this module to speed up our code review and to enforce a high code quality standard, if you want to contribute by submitting a pull request, please read [Pre-Commit & Pr-Check & Test](#Pre-Commit--Pr-Check--Test) section, or your pull request might be rejected by CI pipeline. + +A pull request will be reviewed when it has passed Pre Pull Request Check in the pipeline, and will be merged when it has passed the acceptance tests. Once the ci Pipeline failed, please read the pipeline's output, thanks for your cooperation. + +v4.0.0 is a major version upgrade. Extreme caution must be taken during the upgrade to avoid resource replacement and downtime by accident. + +Running the `terraform plan` first to inspect the plan is strongly advised. ## Deploys 1+ Virtual Machines to your provided VNet @@ -11,6 +45,7 @@ This Terraform module deploys Virtual Machines in Azure with the following chara - Network Security Group (NSG) created with a single remote access rule which opens `var.remote_port` port or auto calculated port number if using `var.vm_os_simple` to all nics - VM nics attached to a single virtual network subnet of your choice (new or existing) via `var.vnet_subnet_id`. - Control the number of Public IP addresses assigned to VMs via `var.nb_public_ip`. Create and attach one Public IP per VM up to the number of VMs or create NO public IPs via setting `var.nb_public_ip` to `0`. +- Control SKU and Allocation Method of the public IPs via `var.allocation_method` and `var.public_ip_sku`. > Note: Terraform module registry is incorrect in the number of required parameters since it only deems required based on variables with non-existent values. The actual minimum required variables depends on the configuration and is specified below in the usage. @@ -124,7 +159,7 @@ More specifically this provisions: 1 - New vnet for all vms -2 - Ubuntu 14.04 Server VMs using `vm_os_publisher`, `vm_os_offer` and `vm_os_sku` which is configured with: +2 - Ubuntu 18.04 Server VMs using `vm_os_publisher`, `vm_os_offer` and `vm_os_sku` which is configured with: - No public IP assigned, so access can only happen through another machine on the vnet. - Opens up port 22 for SSH access with the default ~/.ssh/id_rsa.pub key @@ -136,13 +171,29 @@ More specifically this provisions: 2 - Windows Server 2012 R2 VMs using `vm_os_publisher`, `vm_os_offer` and `vm_os_sku` which is configured with: - Two Public IP addresses (one for each VM) +- Public IP Addresses allocation method is Static and SKU is Standard - Opens up port 3389 for RDP access using the password as shown 3 - New features are supported in v3.0.0: - "nb_data_disk" Number of the data disks attached to each virtual machine -- "enable_ssh_key" Enable ssh key authentication in Linux virtual Machine +- "enable_ssh_key" Enable ssh key authentication in Linux virtual Machine. + When ssh keys are enabled you can either + - use the default "~/.ssh/id_rsa.pub" + - set one key by setting a path in ssh_key variable. e.g "joey_id_rsa.pub" + - set ssh_key and add zero or more files paths in extra_ssh_keys variable e.g. ["ross_id_rsa.pub", "rachel_id_rsa.pub"] (since v3.8.0) + - set ssh_key_values as a list of raw public ssh keys values or refer it to a data source with the public key value, e.g. `["ssh-rsa AAAAB3NzaC1yc..."]` + +4 - You can install custom certificates / secrets on the virtual machine from Key Vault by using the variable `os_profile_secrets`. + +The variable accepts a list of maps with the following keys: + +* source_vault_id : The ID of the Key Vault Secret which contains the encrypted Certificate. +* certificate_url : The certificate URL in Key Vault +* certificate_store : The certificate store on the Virtual Machine where the certificate should be added to (Windows Only). + +In the below example we use the data sources `azurerm_key_vault` and `azurerm_key_vault_certificate` to fetch the certificate information from Key Vault and add it to `windowsservers` via `os_profile_secrets` parameter. ```hcl provider "azurerm" { @@ -154,24 +205,36 @@ resource "azurerm_resource_group" "example" { location = "West Europe" } +data "azurerm_key_vault" "example" { + name = "examplekeyvault" + resource_group_name = azurerm_resource_group.example.name +} + +data "azurerm_key_vault_certificate" "example" { + name = "example-kv-cert" + key_vault_id = data.azurerm_key_vault.example.id +} + module "linuxservers" { - source = "Azure/compute/azurerm" - resource_group_name = azurerm_resource_group.example.name - vm_hostname = "mylinuxvm" - nb_public_ip = 0 - remote_port = "22" - nb_instances = 2 - vm_os_publisher = "Canonical" - vm_os_offer = "UbuntuServer" - vm_os_sku = "18.04-LTS" - vnet_subnet_id = module.network.vnet_subnets[0] - boot_diagnostics = true - delete_os_disk_on_termination = true - nb_data_disk = 2 - data_disk_size_gb = 64 - data_sa_type = "Premium_LRS" - enable_ssh_key = true - vm_size = "Standard_D4s_v3" + source = "Azure/compute/azurerm" + resource_group_name = azurerm_resource_group.example.name + vm_hostname = "mylinuxvm" + nb_public_ip = 0 + remote_port = "22" + nb_instances = 2 + vm_os_publisher = "Canonical" + vm_os_offer = "UbuntuServer" + vm_os_sku = "18.04-LTS" + vnet_subnet_id = module.network.vnet_subnets[0] + boot_diagnostics = true + delete_os_disk_on_termination = true + nb_data_disk = 2 + data_disk_size_gb = 64 + data_sa_type = "Premium_LRS" + enable_ssh_key = true + ssh_key_values = ["ssh-rsa AAAAB3NzaC1yc2EAAAAD..."] + vm_size = "Standard_D4s_v3" + delete_data_disks_on_termination = true tags = { environment = "dev" @@ -187,6 +250,8 @@ module "windowsservers" { vm_hostname = "mywinvm" is_windows_image = true admin_password = "ComplxP@ssw0rd!" + allocation_method = "Static" + public_ip_sku = "Standard" public_ip_dns = ["winterravmip", "winterravmip1"] nb_public_ip = 2 remote_port = "3389" @@ -199,6 +264,23 @@ module "windowsservers" { enable_accelerated_networking = true license_type = "Windows_Client" identity_type = "SystemAssigned" // can be empty, SystemAssigned or UserAssigned + + extra_disks = [ + { + size = 50 + name = "logs" + }, + { + size = 200 + name = "backup" + } + ] + + os_profile_secrets = [{ + source_vault_id = data.azurerm_key_vault.example.id + certificate_url = data.azurerm_key_vault_certificate.example.secret_id + certificate_store = "My" + }] } module "network" { @@ -226,75 +308,93 @@ output "windows_vm_private_ips" { ``` -## Test +## Pre-Commit & Pr-Check & Test ### Configurations - [Configure Terraform for Azure](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/terraform-install-configure) -- [Generate and add SSH Key](https://help.github.com/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent/) Save the key in ~/.ssh/id_rsa. This is not required for Windows deployments. -We provide 2 ways to build, run, and test the module on a local development machine. [Native (Mac/Linux)](#native-maclinux) or [Docker](#docker). +We assumed that you have setup service principal's credentials in your environment variables like below: -### Native (Mac/Linux) +```shell +export ARM_SUBSCRIPTION_ID="" +export ARM_TENANT_ID="" +export ARM_CLIENT_ID="" +export ARM_CLIENT_SECRET="" +``` -#### Prerequisites +On Windows Powershell: -- [Ruby **(~> 2.3)**](https://www.ruby-lang.org/en/downloads/) -- [Bundler **(~> 1.15)**](https://bundler.io/) -- [Terraform **(~> 0.11.7)**](https://www.terraform.io/downloads.html) -- [Golang **(~> 1.10.3)**](https://golang.org/dl/) +```shell +$env:ARM_SUBSCRIPTION_ID="" +$env:ARM_TENANT_ID="" +$env:ARM_CLIENT_ID="" +$env:ARM_CLIENT_SECRET="" +``` -#### Quick Run +We provide a docker image to run the pre-commit checks and tests for you: `mcr.microsoft.com/azterraform:latest` -We provide simple script to quickly set up module development environment: +To run the pre-commit task, we can run the following command: -```sh -$ curl -sSL https://raw.githubusercontent.com/Azure/terramodtest/master/tool/env_setup.sh | sudo bash +```shell +$ docker run --rm -v $(pwd):/src -w /src mcr.microsoft.com/azterraform:latest make pre-commit ``` -Then simply run it in local shell: +On Windows Powershell: -```sh -$ cd $GOPATH/src/{directory_name}/ -$ bundle install -$ rake build -$ rake e2e +```shell +$ docker run --rm -v ${pwd}:/src -w /src mcr.microsoft.com/azterraform:latest make pre-commit ``` -### Docker +NOTE: If an error occurs in Powershell that indicates `Argument or block definition required` for `unit-fixture/locals.tf` and/or `unit-fixture/variables.tf`, the issue could be that the symlink is not configured properly. This can be fixed as described in [this link](https://stackoverflow.com/questions/5917249/git-symbolic-links-in-windows/59761201#59761201): -We provide a Dockerfile to build a new image based `FROM` the `microsoft/terraform-test` Docker hub image which adds additional tools / packages specific for this module (see Custom Image section). Alternatively use only the `microsoft/terraform-test` Docker hub image [by using these instructions](https://github.com/Azure/terraform-test). +```shell +$ git config core.symlinks true +``` -#### Prerequisites +Then switch branches, or execute git reset: -- [Docker](https://www.docker.com/community-edition#/download) +```shell +$ git reset --hard HEAD +``` + +In pre-commit task, we will: -#### Custom Image +1. Run `terraform fmt -recursive` command for your Terraform code. +2. Run `terrafmt fmt -f` command for markdown files and go code files to ensure that the Terraform code embedded in these files are well formatted. +3. Run `go mod tidy` and `go mod vendor` for test folder to ensure that all the dependencies have been synced. +4. Run `gofmt` for all go code files. +5. Run `gofumpt` for all go code files. +6. Run `terraform-docs` on `README.md` file, then run `markdown-table-formatter` to format markdown tables in `README.md`. -This builds the custom image: +Then we can run the pr-check task to check whether our code meets our pipeline's requirement(We strongly recommend you run the following command before you commit): -```sh -$ docker build --build-arg BUILD_ARM_SUBSCRIPTION_ID=$ARM_SUBSCRIPTION_ID --build-arg BUILD_ARM_CLIENT_ID=$ARM_CLIENT_ID --build-arg BUILD_ARM_CLIENT_SECRET=$ARM_CLIENT_SECRET --build-arg BUILD_ARM_TENANT_ID=$ARM_TENANT_ID -t azure-compute . +```shell +$ docker run --rm -v $(pwd):/src -w /src mcr.microsoft.com/azterraform:latest make pr-check ``` -This runs the build and unit tests: +On Windows Powershell: -```sh -$ docker run --rm azure-compute /bin/bash -c "bundle install && rake build" +```shell +$ docker run --rm -v ${pwd}:/src -w /src mcr.microsoft.com/azterraform:latest make pr-check ``` -This runs the end to end tests: +To run the e2e-test, we can run the following command: -```sh -$ docker run --rm azure-compute /bin/bash -c "bundle install && rake e2e" +```text +docker run --rm -v $(pwd):/src -w /src -e ARM_SUBSCRIPTION_ID -e ARM_TENANT_ID -e ARM_CLIENT_ID -e ARM_CLIENT_SECRET mcr.microsoft.com/azterraform:latest make e2e-test ``` -This runs the full tests: +On Windows Powershell: -```sh -$ docker run --rm azure-compute /bin/bash -c "bundle install && rake full" +```text +docker run --rm -v ${pwd}:/src -w /src -e ARM_SUBSCRIPTION_ID -e ARM_TENANT_ID -e ARM_CLIENT_ID -e ARM_CLIENT_SECRET mcr.microsoft.com/azterraform:latest make e2e-test ``` +#### Prerequisites + +- [Docker](https://www.docker.com/community-edition#/download) + ## Authors Originally created by [David Tesar](http://github.com/dtzar) @@ -302,3 +402,136 @@ Originally created by [David Tesar](http://github.com/dtzar) ## License [MIT](LICENSE) + + +## Requirements + +| Name | Version | +|---------------------------------------------------------------------------|----------------| +| [terraform](#requirement\_terraform) | >= 1.3 | +| [azurerm](#requirement\_azurerm) | >= 3.11, < 4.0 | +| [random](#requirement\_random) | >=3.0.0 | + +## Providers + +| Name | Version | +|---------------------------------------------------------------|----------------| +| [azurerm](#provider\_azurerm) | >= 3.11, < 4.0 | +| [random](#provider\_random) | >=3.0.0 | + +## Modules + +| Name | Source | Version | +|--------------------------------------------|--------|---------| +| [os](#module\_os) | ./os | n/a | + +## Resources + +| Name | Type | +|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------| +| [azurerm_availability_set.vm](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/availability_set) | resource | +| [azurerm_managed_disk.vm_data_disk](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/managed_disk) | resource | +| [azurerm_managed_disk.vm_extra_disk](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/managed_disk) | resource | +| [azurerm_network_interface.vm](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_interface) | resource | +| [azurerm_network_interface_security_group_association.test](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_interface_security_group_association) | resource | +| [azurerm_network_security_group.vm](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_security_group) | resource | +| [azurerm_network_security_rule.vm](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_security_rule) | resource | +| [azurerm_public_ip.vm](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/public_ip) | resource | +| [azurerm_storage_account.vm_sa](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/storage_account) | resource | +| [azurerm_virtual_machine.vm_linux](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine) | resource | +| [azurerm_virtual_machine.vm_windows](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine) | resource | +| [azurerm_virtual_machine_data_disk_attachment.vm_data_disk_attachments_linux](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine_data_disk_attachment) | resource | +| [azurerm_virtual_machine_data_disk_attachment.vm_data_disk_attachments_windows](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine_data_disk_attachment) | resource | +| [azurerm_virtual_machine_data_disk_attachment.vm_extra_disk_attachments_linux](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine_data_disk_attachment) | resource | +| [azurerm_virtual_machine_data_disk_attachment.vm_extra_disk_attachments_windows](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine_data_disk_attachment) | resource | +| [azurerm_virtual_machine_extension.extension](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine_extension) | resource | +| [azurerm_virtual_machine_extension.extensions](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine_extension) | resource | +| [random_id.vm_sa](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) | resource | +| [azurerm_public_ip.vm](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/public_ip) | data source | +| [azurerm_resource_group.vm](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/resource_group) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|--------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------|:--------:| +| [admin\_password](#input\_admin\_password) | The admin password to be used on the VMSS that will be deployed. The password must meet the complexity requirements of Azure. | `string` | `""` | no | +| [admin\_username](#input\_admin\_username) | The admin username of the VM that will be deployed. | `string` | `"azureuser"` | no | +| [allocation\_method](#input\_allocation\_method) | Defines how an IP address is assigned. Options are Static or Dynamic. | `string` | `"Dynamic"` | no | +| [as\_platform\_fault\_domain\_count](#input\_as\_platform\_fault\_domain\_count) | (Optional) Specifies the number of fault domains that are used. Defaults to `2`. Changing this forces a new resource to be created. | `number` | `2` | no | +| [as\_platform\_update\_domain\_count](#input\_as\_platform\_update\_domain\_count) | (Optional) Specifies the number of update domains that are used. Defaults to `2`. Changing this forces a new resource to be created. | `number` | `2` | no | +| [availability\_set\_enabled](#input\_availability\_set\_enabled) | (Optional) Enable or Disable availability set. Default is `true` (enabled). | `bool` | `true` | no | +| [boot\_diagnostics](#input\_boot\_diagnostics) | (Optional) Enable or Disable boot diagnostics. | `bool` | `false` | no | +| [boot\_diagnostics\_sa\_type](#input\_boot\_diagnostics\_sa\_type) | (Optional) Storage account type for boot diagnostics. | `string` | `"Standard_LRS"` | no | +| [custom\_data](#input\_custom\_data) | The custom data to supply to the machine. This can be used as a cloud-init for Linux systems. | `string` | `""` | no | +| [data\_disk\_size\_gb](#input\_data\_disk\_size\_gb) | Storage data disk size size. | `number` | `30` | no | +| [data\_sa\_type](#input\_data\_sa\_type) | Data Disk Storage Account type. | `string` | `"Standard_LRS"` | no | +| [delete\_data\_disks\_on\_termination](#input\_delete\_data\_disks\_on\_termination) | Delete data disks when machine is terminated. | `bool` | `false` | no | +| [delete\_os\_disk\_on\_termination](#input\_delete\_os\_disk\_on\_termination) | Delete OS disk when machine is terminated. | `bool` | `false` | no | +| [enable\_accelerated\_networking](#input\_enable\_accelerated\_networking) | (Optional) Enable accelerated networking on Network interface. | `bool` | `false` | no | +| [enable\_ssh\_key](#input\_enable\_ssh\_key) | (Optional) Enable ssh key authentication in Linux virtual Machine. | `bool` | `true` | no | +| [external\_boot\_diagnostics\_storage](#input\_external\_boot\_diagnostics\_storage) | (Optional) The Storage Account's Blob Endpoint which should hold the virtual machine's diagnostic files. Set this argument would disable the creation of `azurerm_storage_account` resource. |
object({
uri = string
})
| `null` | no | +| [extra\_disks](#input\_extra\_disks) | (Optional) List of extra data disks attached to each virtual machine. |
list(object({
name = string
size = number
}))
| `[]` | no | +| [extra\_ssh\_keys](#input\_extra\_ssh\_keys) | Same as ssh\_key, but allows for setting multiple public keys. Set your first key in ssh\_key, and the extras here. | `list(string)` | `[]` | no | +| [identity\_ids](#input\_identity\_ids) | Specifies a list of user managed identity ids to be assigned to the VM. | `list(string)` | `[]` | no | +| [identity\_type](#input\_identity\_type) | The Managed Service Identity Type of this Virtual Machine. | `string` | `""` | no | +| [is\_marketplace\_image](#input\_is\_marketplace\_image) | Boolean flag to notify when the image comes from the marketplace. | `bool` | `false` | no | +| [is\_windows\_image](#input\_is\_windows\_image) | Boolean flag to notify when the custom image is windows based. | `bool` | `false` | no | +| [license\_type](#input\_license\_type) | Specifies the BYOL Type for this Virtual Machine. This is only applicable to Windows Virtual Machines. Possible values are Windows\_Client and Windows\_Server | `string` | `null` | no | +| [location](#input\_location) | (Optional) The location in which the resources will be created. | `string` | `null` | no | +| [managed\_data\_disk\_encryption\_set\_id](#input\_managed\_data\_disk\_encryption\_set\_id) | (Optional) The disk encryption set ID for the managed data disk attached using the azurerm\_virtual\_machine\_data\_disk\_attachment resource. | `string` | `null` | no | +| [name\_template\_availability\_set](#input\_name\_template\_availability\_set) | The name template for the availability set. The following replacements are automatically made: `${vm_hostname}` => `var.vm_hostname`. All other text can be set as desired. | `string` | `"${vm_hostname}-avset"` | no | +| [name\_template\_data\_disk](#input\_name\_template\_data\_disk) | The name template for the data disks. The following replacements are automatically made: `${vm_hostname}` => `var.vm_hostname`, `${host_number}` => 'host index', `${data_disk_number}` => 'data disk index'. All other text can be set as desired. | `string` | `"${vm_hostname}-datadisk-${host_number}-${data_disk_number}"` | no | +| [name\_template\_extra\_disk](#input\_name\_template\_extra\_disk) | The name template for the extra disks. The following replacements are automatically made: `${vm_hostname}` => `var.vm_hostname`, `${host_number}` => 'host index', `${extra_disk_name}` => 'name of extra disk'. All other text can be set as desired. | `string` | `"${vm_hostname}-extradisk-${host_number}-${extra_disk_name}"` | no | +| [name\_template\_network\_interface](#input\_name\_template\_network\_interface) | The name template for the network interface. The following replacements are automatically made: `${vm_hostname}` => `var.vm_hostname`, `${host_number}` => 'host index'. All other text can be set as desired. | `string` | `"${vm_hostname}-nic-${host_number}"` | no | +| [name\_template\_network\_security\_group](#input\_name\_template\_network\_security\_group) | The name template for the network security group. The following replacements are automatically made: `${vm_hostname}` => `var.vm_hostname`. All other text can be set as desired. | `string` | `"${vm_hostname}-nsg"` | no | +| [name\_template\_public\_ip](#input\_name\_template\_public\_ip) | The name template for the public ip. The following replacements are automatically made: `${vm_hostname}` => `var.vm_hostname`, `${ip_number}` => 'public ip index'. All other text can be set as desired. | `string` | `"${vm_hostname}-pip-${ip_number}"` | no | +| [name\_template\_vm\_linux](#input\_name\_template\_vm\_linux) | The name template for the Linux virtual machine. The following replacements are automatically made: `${vm_hostname}` => `var.vm_hostname`, `${host_number}` => 'host index'. All other text can be set as desired. | `string` | `"${vm_hostname}-vmLinux-${host_number}"` | no | +| [name\_template\_vm\_linux\_os\_disk](#input\_name\_template\_vm\_linux\_os\_disk) | The name template for the Linux VM OS disk. The following replacements are automatically made: `${vm_hostname}` => `var.vm_hostname`, `${host_number}` => 'host index'. All other text can be set as desired. | `string` | `"osdisk-${vm_hostname}-${host_number}"` | no | +| [name\_template\_vm\_windows](#input\_name\_template\_vm\_windows) | The name template for the Windows virtual machine. The following replacements are automatically made: `${vm_hostname}` => `var.vm_hostname`, `${host_number}` => 'host index'. All other text can be set as desired. | `string` | `"${vm_hostname}-vmWindows-${host_number}"` | no | +| [name\_template\_vm\_windows\_os\_disk](#input\_name\_template\_vm\_windows\_os\_disk) | The name template for the Windows VM OS disk. The following replacements are automatically made: `${vm_hostname}` => `var.vm_hostname`, `${host_number}` => 'host index'. All other text can be set as desired. | `string` | `"${vm_hostname}-osdisk-${host_number}"` | no | +| [nb\_data\_disk](#input\_nb\_data\_disk) | (Optional) Number of the data disks attached to each virtual machine. | `number` | `0` | no | +| [nb\_instances](#input\_nb\_instances) | Specify the number of vm instances. | `number` | `1` | no | +| [nb\_public\_ip](#input\_nb\_public\_ip) | Number of public IPs to assign corresponding to one IP per vm. Set to 0 to not assign any public IP addresses. | `number` | `1` | no | +| [nested\_data\_disks](#input\_nested\_data\_disks) | (Optional) When `true`, use nested data disks directly attached to the VM. When `false`, use azurerm\_virtual\_machine\_data\_disk\_attachment resource to attach the data disks after the VM is created. Default is `true`. | `bool` | `true` | no | +| [network\_security\_group](#input\_network\_security\_group) | The network security group we'd like to bind with virtual machine. Set this variable will disable the creation of `azurerm_network_security_group` and `azurerm_network_security_rule` resources. |
object({
id = string
})
| `null` | no | +| [os\_profile\_secrets](#input\_os\_profile\_secrets) | Specifies a list of certificates to be installed on the VM, each list item is a map with the keys source\_vault\_id, certificate\_url and certificate\_store. | `list(map(string))` | `[]` | no | +| [public\_ip\_dns](#input\_public\_ip\_dns) | Optional globally unique per datacenter region domain name label to apply to each public ip address. e.g. thisvar.varlocation.cloudapp.azure.com where you specify only thisvar here. This is an array of names which will pair up sequentially to the number of public ips defined in var.nb\_public\_ip. One name or empty string is required for every public ip. If no public ip is desired, then set this to an array with a single empty string. | `list(string)` |
[
null
]
| no | +| [public\_ip\_sku](#input\_public\_ip\_sku) | Defines the SKU of the Public IP. Accepted values are Basic and Standard. Defaults to Basic. | `string` | `"Basic"` | no | +| [remote\_port](#input\_remote\_port) | Remote tcp port to be used for access to the vms created via the nsg applied to the nics. | `string` | `""` | no | +| [resource\_group\_name](#input\_resource\_group\_name) | The name of the resource group in which the resources will be created. | `string` | n/a | yes | +| [source\_address\_prefixes](#input\_source\_address\_prefixes) | (Optional) List of source address prefixes allowed to access var.remote\_port. | `list(string)` |
[
"0.0.0.0/0"
]
| no | +| [ssh\_key](#input\_ssh\_key) | Path to the public key to be used for ssh access to the VM. Only used with non-Windows vms and can be left as-is even if using Windows vms. If specifying a path to a certification on a Windows machine to provision a linux vm use the / in the path versus backslash.e.g. c : /home/id\_rsa.pub. | `string` | `"~/.ssh/id_rsa.pub"` | no | +| [ssh\_key\_values](#input\_ssh\_key\_values) | List of Public SSH Keys values to be used for ssh access to the VMs. | `list(string)` | `[]` | no | +| [storage\_account\_type](#input\_storage\_account\_type) | Defines the type of storage account to be created. Valid options are Standard\_LRS, Standard\_ZRS, Standard\_GRS, Standard\_RAGRS, Premium\_LRS. | `string` | `"Premium_LRS"` | no | +| [storage\_os\_disk\_size\_gb](#input\_storage\_os\_disk\_size\_gb) | (Optional) Specifies the size of the data disk in gigabytes. | `number` | `null` | no | +| [tags](#input\_tags) | A map of the tags to use on the resources that are deployed with this module. | `map(string)` |
{
"source": "terraform"
}
| no | +| [vm\_extension](#input\_vm\_extension) | (Deprecated) This variable has been superseded by the `vm_extensions`. Argument to create `azurerm_virtual_machine_extension` resource, the argument descriptions could be found at [the document](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine_extension). |
object({
name = string
publisher = string
type = string
type_handler_version = string
auto_upgrade_minor_version = optional(bool)
automatic_upgrade_enabled = optional(bool)
failure_suppression_enabled = optional(bool, false)
settings = optional(string)
protected_settings = optional(string)
protected_settings_from_key_vault = optional(object({
secret_url = string
source_vault_id = string
}))
})
| `null` | no | +| [vm\_extensions](#input\_vm\_extensions) | Argument to create `azurerm_virtual_machine_extension` resource, the argument descriptions could be found at [the document](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine_extension). |
set(object({
name = string
publisher = string
type = string
type_handler_version = string
auto_upgrade_minor_version = optional(bool)
automatic_upgrade_enabled = optional(bool)
failure_suppression_enabled = optional(bool, false)
settings = optional(string)
protected_settings = optional(string)
protected_settings_from_key_vault = optional(object({
secret_url = string
source_vault_id = string
}))
}))
| `[]` | no | +| [vm\_hostname](#input\_vm\_hostname) | local name of the Virtual Machine. | `string` | `"myvm"` | no | +| [vm\_os\_id](#input\_vm\_os\_id) | The resource ID of the image that you want to deploy if you are using a custom image.Note, need to provide is\_windows\_image = true for windows custom images. | `string` | `""` | no | +| [vm\_os\_offer](#input\_vm\_os\_offer) | The name of the offer of the image that you want to deploy. This is ignored when vm\_os\_id or vm\_os\_simple are provided. | `string` | `""` | no | +| [vm\_os\_publisher](#input\_vm\_os\_publisher) | The name of the publisher of the image that you want to deploy. This is ignored when vm\_os\_id or vm\_os\_simple are provided. | `string` | `""` | no | +| [vm\_os\_simple](#input\_vm\_os\_simple) | Specify UbuntuServer, WindowsServer, RHEL, openSUSE-Leap, CentOS, Debian, CoreOS and SLES to get the latest image version of the specified os. Do not provide this value if a custom value is used for vm\_os\_publisher, vm\_os\_offer, and vm\_os\_sku. | `string` | `""` | no | +| [vm\_os\_sku](#input\_vm\_os\_sku) | The sku of the image that you want to deploy. This is ignored when vm\_os\_id or vm\_os\_simple are provided. | `string` | `""` | no | +| [vm\_os\_version](#input\_vm\_os\_version) | The version of the image that you want to deploy. This is ignored when vm\_os\_id or vm\_os\_simple are provided. | `string` | `"latest"` | no | +| [vm\_size](#input\_vm\_size) | Specifies the size of the virtual machine. | `string` | `"Standard_D2s_v3"` | no | +| [vnet\_subnet\_id](#input\_vnet\_subnet\_id) | The subnet id of the virtual network where the virtual machines will reside. | `string` | n/a | yes | +| [zone](#input\_zone) | (Optional) The Availability Zone which the Virtual Machine should be allocated in, only one zone would be accepted. If set then this module won't create `azurerm_availability_set` resource. Changing this forces a new resource to be created. | `string` | `null` | no | + +## Outputs + +| Name | Description | +|--------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------| +| [availability\_set\_id](#output\_availability\_set\_id) | Id of the availability set where the vms are provisioned. If `var.zones` is set, this output will return empty string. | +| [network\_interface\_ids](#output\_network\_interface\_ids) | ids of the vm nics provisoned. | +| [network\_interface\_private\_ip](#output\_network\_interface\_private\_ip) | private ip addresses of the vm nics | +| [network\_security\_group\_id](#output\_network\_security\_group\_id) | id of the security group provisioned | +| [network\_security\_group\_name](#output\_network\_security\_group\_name) | name of the security group provisioned, empty if no security group was created. | +| [public\_ip\_address](#output\_public\_ip\_address) | The actual ip address allocated for the resource. | +| [public\_ip\_dns\_name](#output\_public\_ip\_dns\_name) | fqdn to connect to the first vm provisioned. | +| [public\_ip\_id](#output\_public\_ip\_id) | id of the public ip address provisoned. | +| [vm\_identity](#output\_vm\_identity) | map with key `Virtual Machine Id`, value `list of identity` created for the Virtual Machine. | +| [vm\_ids](#output\_vm\_ids) | Virtual machine ids created. | +| [vm\_names](#output\_vm\_names) | Virtual machine names created. | +| [vm\_zones](#output\_vm\_zones) | map with key `Virtual Machine Id`, value `list of the Availability Zone` which the Virtual Machine should be allocated in. | + diff --git a/Rakefile b/Rakefile deleted file mode 100644 index 3a22fc1..0000000 --- a/Rakefile +++ /dev/null @@ -1,50 +0,0 @@ -# Official gems. -require 'colorize' -require 'rspec/core/rake_task' - -# Git repo gems. -require 'bundler/setup' -require 'terramodtest' - -namespace :static do - task :style do - style_tf - end - task :lint do - lint_tf - end - task :format do - format_tf - end -end - -namespace :integration do - task :ensure do - success = system ("dep ensure") - if not success - raise "ERROR: Dep ensure failed!\n".red - end - end - task :test do - success = system ("go test -v ./test/ -timeout 30m -args azureuser ~/.ssh/id_rsa") - if not success - raise "ERROR: Go test failed!\n".red - end - end -end - -task :prereqs => [] - -task :validate => [ 'static:style', 'static:lint' ] - -task :format => [ 'static:format' ] - -task :build => [ 'prereqs', 'validate' ] - -task :unit => [] - -task :e2e => [ 'integration:ensure', 'integration:test' ] - -task :default => [ 'build' ] - -task :full => [ 'build', 'unit', 'e2e' ] diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..869fdfe --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,41 @@ + + +## Security + +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), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). + +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/opensource/security/definition), please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). + +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/opensource/security/pgpkey). + +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://aka.ms/opensource/security/msrc). + +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: + + * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +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/opensource/security/bounty) page for more details about our active programs. + +## Preferred Languages + +We prefer all communications to be in English. + +## Policy + +Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). + + diff --git a/bug_report.md b/bug_report.md deleted file mode 100644 index 029eacd..0000000 --- a/bug_report.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve ---- - - - - - - -- Terraform Version: -- OS Version: - -Bug description: - -Steps to reproduce: - -1. -2. - diff --git a/examples/complete/TestRecord.md b/examples/complete/TestRecord.md new file mode 100644 index 0000000..220dc3f --- /dev/null +++ b/examples/complete/TestRecord.md @@ -0,0 +1,108 @@ +## 27 Feb 23 02:57 UTC + +Success: true + +### Versions + +Terraform v1.3.8 +on linux_amd64 ++ provider registry.terraform.io/anschoewe/curl v1.0.2 ++ provider registry.terraform.io/hashicorp/azurerm v3.45.0 ++ provider registry.terraform.io/hashicorp/random v3.4.3 + +### Error + + + +--- + +## 21 Feb 23 06:59 UTC + +Success: true + +### Versions + +Terraform v1.3.7 +on linux_amd64 ++ provider registry.terraform.io/anschoewe/curl v1.0.2 ++ provider registry.terraform.io/hashicorp/azurerm v3.44.1 ++ provider registry.terraform.io/hashicorp/random v3.4.3 + +### Error + + + +--- + +## 20 Feb 23 02:24 UTC + +Success: true + +### Versions + +Terraform v1.3.7 +on linux_amd64 ++ provider registry.terraform.io/anschoewe/curl v1.0.2 ++ provider registry.terraform.io/hashicorp/azurerm v3.44.1 ++ provider registry.terraform.io/hashicorp/random v3.4.3 + +### Error + + + +--- + +## 19 Feb 23 00:26 UTC + +Success: true + +### Versions + +Terraform v1.3.7 +on linux_amd64 ++ provider registry.terraform.io/anschoewe/curl v1.0.2 ++ provider registry.terraform.io/hashicorp/azurerm v3.44.1 ++ provider registry.terraform.io/hashicorp/random v3.4.3 + +### Error + + + +--- + +## 17 Feb 23 02:50 UTC + +Success: true + +### Versions + +Terraform v1.3.7 +on linux_amd64 ++ provider registry.terraform.io/anschoewe/curl v1.0.2 ++ provider registry.terraform.io/hashicorp/azurerm v3.43.0 ++ provider registry.terraform.io/hashicorp/random v3.4.3 + +### Error + + + +--- + +## 17 Feb 23 01:43 UTC + +Success: true + +### Versions + +Terraform v1.3.7 +on linux_amd64 ++ provider registry.terraform.io/anschoewe/curl v1.0.2 ++ provider registry.terraform.io/hashicorp/azurerm v3.43.0 ++ provider registry.terraform.io/hashicorp/random v3.4.3 + +### Error + + + +--- + diff --git a/examples/complete/key_vault.tf b/examples/complete/key_vault.tf new file mode 100644 index 0000000..9e38c0a --- /dev/null +++ b/examples/complete/key_vault.tf @@ -0,0 +1,189 @@ +data "curl" "public_ip" { + count = var.key_vault_firewall_bypass_ip_cidr == null ? 1 : 0 + + http_method = "GET" + uri = "https://api.ipify.org?format=json" +} + +locals { + # We cannot use coalesce here because it's not short-circuit and public_ip's index will cause error + public_ip = var.key_vault_firewall_bypass_ip_cidr == null ? jsondecode(data.curl.public_ip[0].response).ip : var.key_vault_firewall_bypass_ip_cidr +} + +data "azurerm_client_config" "current" {} + +resource "azurerm_key_vault" "test" { + location = var.location_alt + name = "test${random_id.ip_dns.hex}kv" + resource_group_name = azurerm_resource_group.test.name + sku_name = "premium" + tenant_id = data.azurerm_client_config.current.tenant_id + enabled_for_deployment = true + enabled_for_disk_encryption = true + purge_protection_enabled = true + soft_delete_retention_days = 7 + + network_acls { + bypass = "AzureServices" + default_action = "Deny" + ip_rules = [local.public_ip] + } +} + +resource "azurerm_key_vault_access_policy" "current_user" { + key_vault_id = azurerm_key_vault.test.id + object_id = coalesce(var.managed_identity_principal_id, data.azurerm_client_config.current.object_id) + tenant_id = data.azurerm_client_config.current.tenant_id + certificate_permissions = [ + "Create", + "Delete", + "DeleteIssuers", + "Get", + "GetIssuers", + "Import", + "List", + "ListIssuers", + "ManageContacts", + "ManageIssuers", + "SetIssuers", + "Update", + ] + key_permissions = [ + "Backup", + "Create", + "Decrypt", + "Delete", + "Encrypt", + "Get", + "Import", + "List", + "Purge", + "Recover", + "Restore", + "Sign", + "UnwrapKey", + "Update", + "Verify", + "WrapKey", + ] + secret_permissions = [ + "Backup", + "Delete", + "Get", + "List", + "Purge", + "Recover", + "Restore", + "Set", + ] + + timeouts { + delete = "45m" + update = "45m" + } +} + +resource "azurerm_key_vault_access_policy" "test_vm" { + key_vault_id = azurerm_key_vault.test.id + object_id = azurerm_user_assigned_identity.vm.principal_id + tenant_id = data.azurerm_client_config.current.tenant_id + certificate_permissions = [ + "Get", + ] + key_permissions = [ + "Get", + ] + secret_permissions = [ + "Get", + ] + + timeouts { + delete = "45m" + update = "45m" + } +} + +resource "azurerm_key_vault_access_policy" "storage_account" { + key_vault_id = azurerm_key_vault.test.id + object_id = azurerm_user_assigned_identity.storage_account.principal_id + tenant_id = data.azurerm_client_config.current.tenant_id + + key_permissions = [ + "Get", + "WrapKey", + "UnwrapKey", + ] +} + +resource "azurerm_key_vault_certificate" "test" { + key_vault_id = azurerm_key_vault.test.id + name = "test${random_id.ip_dns.hex}kvcert" + + certificate_policy { + issuer_parameters { + name = "Self" + } + key_properties { + exportable = true + key_type = "RSA" + reuse_key = true + key_size = 2048 + } + lifetime_action { + action { + action_type = "AutoRenew" + } + trigger { + days_before_expiry = 30 + } + } + secret_properties { + content_type = "application/x-pkcs12" + } + x509_certificate_properties { + key_usage = [ + "cRLSign", + "dataEncipherment", + "digitalSignature", + "keyAgreement", + "keyCertSign", + "keyEncipherment", + ] + subject = "CN=hello-world" + validity_in_months = 12 + # Server Authentication = 1.3.6.1.5.5.7.3.1 + # Client Authentication = 1.3.6.1.5.5.7.3.2 + extended_key_usage = ["1.3.6.1.5.5.7.3.1"] + + subject_alternative_names { + dns_names = ["internal.contoso.com", "domain.hello.world"] + } + } + } + + depends_on = [azurerm_key_vault_access_policy.current_user, azurerm_key_vault_access_policy.test_vm] +} + +resource "azurerm_key_vault_key" "des_key" { + key_opts = [ + "decrypt", + "encrypt", + "sign", + "unwrapKey", + "verify", + "wrapKey", + ] + key_type = "RSA-HSM" + key_vault_id = azurerm_key_vault.test.id + name = "des-key" + expiration_date = timeadd("${formatdate("YYYY-MM-DD", timestamp())}T00:00:00Z", "168h") + key_size = 2048 + + depends_on = [ + azurerm_key_vault_access_policy.current_user + ] + + lifecycle { + ignore_changes = [expiration_date] + } +} \ No newline at end of file diff --git a/examples/complete/main.tf b/examples/complete/main.tf new file mode 100644 index 0000000..f38e046 --- /dev/null +++ b/examples/complete/main.tf @@ -0,0 +1,241 @@ +resource "random_id" "ip_dns" { + byte_length = 4 +} + +resource "azurerm_resource_group" "test" { + location = var.location + name = "host${random_id.ip_dns.hex}-rg" +} + +locals { + vnet_address_space = "10.0.0.0/16" +} + +resource "azurerm_virtual_network" "vnet" { + address_space = [local.vnet_address_space] + location = var.location_alt + name = "host${random_id.ip_dns.hex}-vn" + resource_group_name = azurerm_resource_group.test.name +} + +resource "azurerm_subnet" "subnet" { + count = 3 + + # tflint-ignore: terraform_count_index_usage + address_prefixes = [cidrsubnet(local.vnet_address_space, 8, count.index)] + name = "host${random_id.ip_dns.hex}-sn-${count.index + 1}" + resource_group_name = azurerm_resource_group.test.name + virtual_network_name = azurerm_virtual_network.vnet.name +} + +resource "azurerm_user_assigned_identity" "vm" { + location = azurerm_resource_group.test.location + name = "host${random_id.ip_dns.hex}-id" + resource_group_name = azurerm_resource_group.test.name +} + +resource "azurerm_user_assigned_identity" "storage_account" { + location = azurerm_resource_group.test.location + name = "storage-account-${random_id.ip_dns.hex}-id" + resource_group_name = azurerm_resource_group.test.name +} + +locals { + ubuntu_ssh_keys = fileexists("~/.ssh/id_rsa.pub") ? [] : ["monica_id_rsa.pub"] +} + +resource "random_password" "admin_password" { + length = 20 + lower = true + min_lower = 1 + min_numeric = 1 + min_special = 1 + min_upper = 1 + numeric = true + special = true + upper = true +} + +locals { + admin_password = coalesce(var.admin_password, random_password.admin_password.result) +} + +module "ubuntuservers" { + source = "../.." + vm_hostname = "${random_id.ip_dns.hex}-u" + resource_group_name = azurerm_resource_group.test.name + location = var.location_alt + admin_username = var.admin_username + admin_password = local.admin_password + vm_os_simple = var.vm_os_simple_1 + public_ip_dns = ["ubuntusimplevmips-${random_id.ip_dns.hex}"] + vnet_subnet_id = azurerm_subnet.subnet[0].id + allocation_method = "Static" + public_ip_sku = "Standard" + enable_accelerated_networking = true + delete_os_disk_on_termination = true + ssh_key = fileexists("~/.ssh/id_rsa.pub") ? "~/.ssh/id_rsa.pub" : "" + extra_ssh_keys = local.ubuntu_ssh_keys + vm_size = "Standard_DS2_V2" + nb_data_disk = 2 + nested_data_disks = false + identity_type = "UserAssigned" + identity_ids = [azurerm_user_assigned_identity.vm.id] + os_profile_secrets = [ + { + source_vault_id = azurerm_key_vault.test.id + certificate_url = azurerm_key_vault_certificate.test.secret_id + } + ] +} + +module "debianservers" { + source = "../.." + vm_hostname = "${random_id.ip_dns.hex}-d" + resource_group_name = azurerm_resource_group.test.name + location = var.location_alt + admin_username = var.admin_username + admin_password = local.admin_password + custom_data = var.custom_data + vm_os_simple = var.vm_os_simple_2 + public_ip_dns = ["debiansimplevmips-${random_id.ip_dns.hex}"] + # change to a unique name per datacenter region + vnet_subnet_id = azurerm_subnet.subnet[1].id + allocation_method = "Static" + delete_data_disks_on_termination = true + delete_os_disk_on_termination = true + enable_ssh_key = true + ssh_key = fileexists("~/.ssh/id_rsa.pub") ? "~/.ssh/id_rsa.pub" : "" + extra_ssh_keys = ["monica_id_rsa.pub"] + extra_disks = [ + { + size = 5 + name = "extra1" + }, + { + size = 5 + name = "extra2" + } + ] + identity_type = "UserAssigned" + identity_ids = [azurerm_user_assigned_identity.vm.id] +} + +resource "random_string" "storage_account" { + length = 16 + lower = true + upper = false + numeric = true + min_lower = 1 + min_numeric = 1 + special = false +} + +resource "azurerm_storage_account" "debian2" { + account_replication_type = "LRS" + account_tier = "Standard" + location = var.location_alt + name = "bootdiag${random_string.storage_account.result}" + resource_group_name = azurerm_resource_group.test.name + account_kind = "StorageV2" + min_tls_version = "TLS1_2" + public_network_access_enabled = false + + customer_managed_key { + key_vault_key_id = azurerm_key_vault_key.des_key.id + user_assigned_identity_id = azurerm_user_assigned_identity.storage_account.id + } + identity { + type = "UserAssigned" + identity_ids = [azurerm_user_assigned_identity.storage_account.id] + } + network_rules { + default_action = "Deny" + } + queue_properties { + logging { + delete = true + read = true + version = "1.0" + write = true + retention_policy_days = 1 + } + } + + depends_on = [azurerm_key_vault_access_policy.storage_account] +} + +resource "azurerm_network_security_group" "external_nsg" { + location = var.location_alt + name = "${azurerm_resource_group.test.name}-nsg" + resource_group_name = azurerm_resource_group.test.name +} + +resource "azurerm_network_security_rule" "vm" { + access = "Allow" + direction = "Inbound" + name = "allow_remote_ssh" + network_security_group_name = azurerm_network_security_group.external_nsg.name + priority = 101 + protocol = "Tcp" + resource_group_name = azurerm_resource_group.test.name + description = "Allow remote protocol ssh" + destination_address_prefix = "*" + destination_port_range = "22" + source_address_prefix = local.public_ip + source_port_range = "*" +} + +module "debianservers2" { + source = "../.." + vm_hostname = "${random_id.ip_dns.hex}-d2" + resource_group_name = azurerm_resource_group.test.name + location = var.location_alt + admin_username = var.admin_username + allocation_method = "Static" + as_platform_fault_domain_count = 3 + as_platform_update_domain_count = 5 + vm_os_simple = var.vm_os_simple_2 + vnet_subnet_id = azurerm_subnet.subnet[1].id + enable_ssh_key = true + external_boot_diagnostics_storage = { + uri = azurerm_storage_account.debian2.primary_blob_endpoint + } + delete_data_disks_on_termination = true + delete_os_disk_on_termination = true + identity_type = "UserAssigned" + identity_ids = [azurerm_user_assigned_identity.vm.id] + public_ip_sku = "Standard" + ssh_key = "" + ssh_key_values = [file("${path.module}/monica_id_rsa.pub")] + network_security_group = { + id = azurerm_network_security_group.external_nsg.id + } + # To test `var.zone` please uncomment the line below. + # zone = "2" +} + +module "windowsservers" { + source = "../.." + vm_hostname = "${random_id.ip_dns.hex}-w" # line can be removed if only one VM module per resource group + resource_group_name = azurerm_resource_group.test.name + location = var.location_alt + is_windows_image = true + admin_username = var.admin_username + admin_password = local.admin_password + vm_os_simple = "WindowsServer" + public_ip_dns = ["winsimplevmips-${random_id.ip_dns.hex}"] # change to a unique name per datacenter region + vnet_subnet_id = azurerm_subnet.subnet[2].id + license_type = var.license_type + identity_type = "UserAssigned" + identity_ids = [azurerm_user_assigned_identity.vm.id] + nb_data_disk = 1 + nested_data_disks = false + os_profile_secrets = [ + { + source_vault_id = azurerm_key_vault.test.id + certificate_url = azurerm_key_vault_certificate.test.secret_id + certificate_store = "My" + } + ] +} diff --git a/examples/complete/monica_id_rsa.pub b/examples/complete/monica_id_rsa.pub new file mode 100644 index 0000000..2c47ad2 --- /dev/null +++ b/examples/complete/monica_id_rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC8GIRF1Snlg9NKCmM74RHXqRGMXyui088+ntQqkQkFIL/BrlgP3CzOgHQmJ+3f0Up/+9UY9vX7AmT7WxVTyqBHT/Aes3VmU3wLO5/MMV/HRrT4z2QV/80futhxjk2unNdWGvbFcR6Y3I44EJFmr8GMbyXRtr0ibuv8BlTYx/K6AXSJ3V+kBqXMOF1QRvVoX9fJKPKjMsebe0cB1IYlm9KLqtciMy+aFOEsSNfrw5cNVsQfK3BgOUKAHsLfBiR7imA2ca+hh005GEtcVJvpvFzcM+bZggUpdqQwIzk1Kv/tROiJiGS0NnyzoxIZYeM3z/mQ5qnglp+174XGCG66EAnVdf5kbaI0Iu7FpAmVhJ92N+MNKoP6vT8cMkYYZf3RaiMMnzjswK/VLbb5ks6Qe9qEPXW1IBtkaaF7+0PCWbPr86I0G2bOa2tFyOHm046Z9sRlkaOO95hmer6Y6MUbMpfeprmjR87u6MVOPglnARfV3UI9i6wOUhVVIi6Wb424HWU= regina.filange@nana diff --git a/examples/complete/outputs.tf b/examples/complete/outputs.tf new file mode 100644 index 0000000..1e59d41 --- /dev/null +++ b/examples/complete/outputs.tf @@ -0,0 +1,145 @@ +output "debian2_availability_set_id" { + value = module.debianservers2.availability_set_id +} + +output "debian2_nsg_id" { + value = module.debianservers2.network_security_group_id +} + +output "debian2_nsg_name" { + value = module.debianservers2.network_security_group_name +} + +output "debian2_vm_names" { + value = module.debianservers2.vm_names +} + +output "debian_availability_set_id" { + value = module.debianservers.availability_set_id +} + +output "debian_ip_address" { + value = module.debianservers.public_ip_address +} + +output "debian_nsg_id" { + value = module.debianservers.network_security_group_id +} + +output "debian_nsg_name" { + value = module.debianservers.network_security_group_name +} + +output "debian_vm_names" { + value = module.debianservers.vm_names +} + +output "debian_vm_public_name" { + value = module.debianservers.public_ip_dns_name +} + +output "public_ip_dns_names" { + value = toset(concat( + module.ubuntuservers.public_ip_dns_name, + module.debianservers.public_ip_dns_name, + module.debianservers2.public_ip_dns_name, + module.windowsservers.public_ip_dns_name, + )) +} + +output "public_ip_ids" { + value = toset(concat( + module.ubuntuservers.public_ip_id, + module.debianservers.public_ip_id, + module.debianservers2.public_ip_id, + module.windowsservers.public_ip_id, + )) +} + +output "ubuntu_availability_set_id" { + value = module.ubuntuservers.availability_set_id +} + +output "ubuntu_identity_type" { + value = module.ubuntuservers.vm_identity +} + +output "ubuntu_ip_address" { + value = module.ubuntuservers.public_ip_address +} + +output "ubuntu_nsg_id" { + value = module.ubuntuservers.network_security_group_id +} + +output "ubuntu_nsg_name" { + value = module.ubuntuservers.network_security_group_name +} + +output "ubuntu_vm_names" { + value = module.ubuntuservers.vm_names +} + +output "ubuntu_vm_public_name" { + value = module.ubuntuservers.public_ip_dns_name +} + +output "vm_identities" { + value = merge( + module.ubuntuservers.vm_identity, + module.debianservers.vm_identity, + module.debianservers2.vm_identity, + module.windowsservers.vm_identity, + ) +} + +output "vm_ids" { + value = toset(concat( + module.ubuntuservers.vm_ids, + module.debianservers.vm_ids, + module.debianservers2.vm_ids, + module.windowsservers.vm_ids, + )) +} + +output "vm_zones" { + value = merge( + module.ubuntuservers.vm_zones, + module.debianservers.vm_zones, + module.debianservers2.vm_zones, + module.windowsservers.vm_zones, + ) +} + +output "windows_availability_set_id" { + value = module.windowsservers.availability_set_id +} + +output "windows_identity_type" { + value = module.windowsservers.vm_identity +} + +output "windows_ip_address" { + value = module.windowsservers.public_ip_address +} + +output "windows_nsg_id" { + value = module.windowsservers.network_security_group_id +} + +output "windows_nsg_name" { + value = module.windowsservers.network_security_group_name +} + +output "windows_vm_admin_password" { + sensitive = true + value = local.admin_password +} + +output "windows_vm_names" { + value = module.windowsservers.vm_names +} + +output "windows_vm_public_name" { + value = module.windowsservers.public_ip_dns_name +} \ No newline at end of file diff --git a/examples/complete/providers.tf b/examples/complete/providers.tf new file mode 100644 index 0000000..1c36573 --- /dev/null +++ b/examples/complete/providers.tf @@ -0,0 +1,31 @@ +terraform { + required_version = ">= 1.3" + + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = ">= 3.11, < 4.0" + } + curl = { + source = "anschoewe/curl" + version = "1.0.2" + } + random = { + source = "hashicorp/random" + version = ">=3.0.0" + } + } +} + +provider "azurerm" { + features { + key_vault { + purge_soft_delete_on_destroy = false + purge_soft_deleted_certificates_on_destroy = false + purge_soft_deleted_keys_on_destroy = false + } + resource_group { + prevent_deletion_if_contains_resources = false + } + } +} \ No newline at end of file diff --git a/examples/complete/variables.tf b/examples/complete/variables.tf new file mode 100644 index 0000000..3981dae --- /dev/null +++ b/examples/complete/variables.tf @@ -0,0 +1,50 @@ +variable "admin_password" { + type = string + sensitive = true + default = null +} + +variable "admin_username" { + type = string + default = "azureuser" +} + +variable "custom_data" { + type = string + default = "" +} + +variable "key_vault_firewall_bypass_ip_cidr" { + type = string + default = null +} + +variable "license_type" { + type = string + default = "Windows_Client" +} + +variable "location" { + type = string + default = "eastus" +} + +variable "location_alt" { + type = string + default = "eastus2" +} + +variable "managed_identity_principal_id" { + type = string + default = null +} + +variable "vm_os_simple_1" { + type = string + default = "UbuntuServer" +} + +variable "vm_os_simple_2" { + type = string + default = "Debian" +} \ No newline at end of file diff --git a/examples/extensions/TestRecord.md b/examples/extensions/TestRecord.md new file mode 100644 index 0000000..71602f6 --- /dev/null +++ b/examples/extensions/TestRecord.md @@ -0,0 +1,102 @@ +## 27 Feb 23 02:53 UTC + +Success: true + +### Versions + +Terraform v1.3.8 +on linux_amd64 ++ provider registry.terraform.io/hashicorp/azurerm v3.45.0 ++ provider registry.terraform.io/hashicorp/random v3.4.3 + +### Error + + + +--- + +## 21 Feb 23 06:55 UTC + +Success: true + +### Versions + +Terraform v1.3.7 +on linux_amd64 ++ provider registry.terraform.io/hashicorp/azurerm v3.44.1 ++ provider registry.terraform.io/hashicorp/random v3.4.3 + +### Error + + + +--- + +## 20 Feb 23 02:19 UTC + +Success: true + +### Versions + +Terraform v1.3.7 +on linux_amd64 ++ provider registry.terraform.io/hashicorp/azurerm v3.44.1 ++ provider registry.terraform.io/hashicorp/random v3.4.3 + +### Error + + + +--- + +## 19 Feb 23 00:21 UTC + +Success: true + +### Versions + +Terraform v1.3.7 +on linux_amd64 ++ provider registry.terraform.io/hashicorp/azurerm v3.44.1 ++ provider registry.terraform.io/hashicorp/random v3.4.3 + +### Error + + + +--- + +## 17 Feb 23 02:45 UTC + +Success: true + +### Versions + +Terraform v1.3.7 +on linux_amd64 ++ provider registry.terraform.io/hashicorp/azurerm v3.43.0 ++ provider registry.terraform.io/hashicorp/random v3.4.3 + +### Error + + + +--- + +## 17 Feb 23 01:38 UTC + +Success: true + +### Versions + +Terraform v1.3.7 +on linux_amd64 ++ provider registry.terraform.io/hashicorp/azurerm v3.43.0 ++ provider registry.terraform.io/hashicorp/random v3.4.3 + +### Error + + + +--- + diff --git a/examples/extensions/main.tf b/examples/extensions/main.tf new file mode 100644 index 0000000..7f5c440 --- /dev/null +++ b/examples/extensions/main.tf @@ -0,0 +1,55 @@ +resource "random_id" "id" { + byte_length = 4 +} + +resource "azurerm_resource_group" "test" { + location = var.location + name = "host${random_id.id.hex}-rg" +} + +module "vnet" { + source = "Azure/vnet/azurerm" + version = "4.0.0" + + resource_group_name = azurerm_resource_group.test.name + use_for_each = true + vnet_location = azurerm_resource_group.test.location + address_space = ["192.168.0.0/24"] + vnet_name = "vnet-vm-${random_id.id.hex}" + subnet_names = ["subnet-compute"] + subnet_prefixes = ["192.168.0.0/28"] +} + +module "ubuntuservers" { + source = "../.." + vm_hostname = "${random_id.id.hex}-u" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + admin_username = var.admin_username + boot_diagnostics = false + vm_os_simple = var.vm_os_simple + vnet_subnet_id = module.vnet.vnet_subnets[0] + delete_data_disks_on_termination = true + delete_os_disk_on_termination = true + enable_ssh_key = true + ssh_key = "monica_id_rsa.pub" + storage_account_type = "Standard_LRS" + vm_size = "Standard_F2" + nb_data_disk = 1 + vm_extensions = [ + { + name = "hostname" + publisher = "Microsoft.Azure.Extensions", + type = "CustomScript", + type_handler_version = "2.0", + settings = "{\"commandToExecute\": \"hostname && uptime\"}", + }, + { + name = "AzureMonitorLinuxAgent" + publisher = "Microsoft.Azure.Monitor", + type = "AzureMonitorLinuxAgent", + type_handler_version = "1.21", + auto_upgrade_minor_version = true + }, + ] +} \ No newline at end of file diff --git a/examples/extensions/monica_id_rsa.pub b/examples/extensions/monica_id_rsa.pub new file mode 100644 index 0000000..2c47ad2 --- /dev/null +++ b/examples/extensions/monica_id_rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC8GIRF1Snlg9NKCmM74RHXqRGMXyui088+ntQqkQkFIL/BrlgP3CzOgHQmJ+3f0Up/+9UY9vX7AmT7WxVTyqBHT/Aes3VmU3wLO5/MMV/HRrT4z2QV/80futhxjk2unNdWGvbFcR6Y3I44EJFmr8GMbyXRtr0ibuv8BlTYx/K6AXSJ3V+kBqXMOF1QRvVoX9fJKPKjMsebe0cB1IYlm9KLqtciMy+aFOEsSNfrw5cNVsQfK3BgOUKAHsLfBiR7imA2ca+hh005GEtcVJvpvFzcM+bZggUpdqQwIzk1Kv/tROiJiGS0NnyzoxIZYeM3z/mQ5qnglp+174XGCG66EAnVdf5kbaI0Iu7FpAmVhJ92N+MNKoP6vT8cMkYYZf3RaiMMnzjswK/VLbb5ks6Qe9qEPXW1IBtkaaF7+0PCWbPr86I0G2bOa2tFyOHm046Z9sRlkaOO95hmer6Y6MUbMpfeprmjR87u6MVOPglnARfV3UI9i6wOUhVVIi6Wb424HWU= regina.filange@nana diff --git a/examples/extensions/outputs.tf b/examples/extensions/outputs.tf new file mode 100644 index 0000000..e69de29 diff --git a/examples/extensions/providers.tf b/examples/extensions/providers.tf new file mode 100644 index 0000000..8fde8e1 --- /dev/null +++ b/examples/extensions/providers.tf @@ -0,0 +1,27 @@ +terraform { + required_version = ">= 1.3" + + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = ">= 3.11, < 4.0" + } + random = { + source = "hashicorp/random" + version = ">=3.0.0" + } + } +} + +provider "azurerm" { + features { + key_vault { + purge_soft_delete_on_destroy = false + purge_soft_deleted_certificates_on_destroy = false + purge_soft_deleted_keys_on_destroy = false + } + resource_group { + prevent_deletion_if_contains_resources = false + } + } +} \ No newline at end of file diff --git a/examples/extensions/variables.tf b/examples/extensions/variables.tf new file mode 100644 index 0000000..a9809c5 --- /dev/null +++ b/examples/extensions/variables.tf @@ -0,0 +1,14 @@ +variable "admin_username" { + type = string + default = "azureuser" +} + +variable "location" { + type = string + default = "eastus" +} + +variable "vm_os_simple" { + type = string + default = "UbuntuServer" +} \ No newline at end of file diff --git a/locals.tf b/locals.tf new file mode 100644 index 0000000..6f4b080 --- /dev/null +++ b/locals.tf @@ -0,0 +1,34 @@ +locals { + data_disk_list = flatten([ + for host_number in range(var.nb_instances) : [ + for data_disk_number in range(var.nb_data_disk) : { + key = join("-", ["datadisk", host_number, data_disk_number]) + name = replace(replace(replace(var.name_template_data_disk, "$${vm_hostname}", var.vm_hostname), "$${host_number}", host_number), "$${data_disk_number}", data_disk_number) + host_number = host_number + disk_number = data_disk_number + } + ] + ]) + data_disk_map = { for obj in local.data_disk_list : obj.key => obj if !var.nested_data_disks } + data_disk_map_linux = { for obj in local.data_disk_list : obj.key => obj if !var.nested_data_disks && !local.is_windows } + data_disk_map_windows = { for obj in local.data_disk_list : obj.key => obj if !var.nested_data_disks && local.is_windows } + extra_disk_list = flatten([ + for host_number in range(var.nb_instances) : [ + for extra_disk in var.extra_disks : { + key = join("-", ["extradisk", host_number, extra_disk.name]) + name = replace(replace(replace(var.name_template_extra_disk, "$${vm_hostname}", var.vm_hostname), "$${host_number}", host_number), "$${extra_disk_name}", extra_disk.name) + host_number = host_number + disk_number = index(var.extra_disks, extra_disk) + disk_name = extra_disk.name + disk_size = extra_disk.size + } + ] + ]) + extra_disk_map = { for obj in local.extra_disk_list : obj.key => obj if !var.nested_data_disks } + extra_disk_map_linux = { for obj in local.extra_disk_list : obj.key => obj if !var.nested_data_disks && !local.is_windows } + extra_disk_map_windows = { for obj in local.extra_disk_list : obj.key => obj if !var.nested_data_disks && local.is_windows } + is_windows = (var.is_windows_image || contains(tolist([var.vm_os_simple, var.vm_os_offer]), "WindowsServer")) || var.is_windows_image == true + nested_data_disk_list = var.nested_data_disks ? range(var.nb_data_disk) : [] + nested_extra_data_disk_list = var.nested_data_disks ? var.extra_disks : [] + vm_extensions = { for p in setproduct(toset([for e in var.vm_extensions : e]), toset(range(var.nb_instances))) : "${p[0].name}-${p[1]}" => { index = p[1], value = p[0] } } +} \ No newline at end of file diff --git a/main.tf b/main.tf index d620cb9..cd7f375 100644 --- a/main.tf +++ b/main.tf @@ -4,10 +4,22 @@ module "os" { } data "azurerm_resource_group" "vm" { + count = var.location == null ? 1 : 0 + name = var.resource_group_name } -resource "random_id" "vm-sa" { +locals { + location = var.location == null ? data.azurerm_resource_group.vm[0].location : var.location + ssh_keys = compact(concat([var.ssh_key], var.extra_ssh_keys)) +} + +moved { + from = random_id.vm-sa + to = random_id.vm_sa +} + +resource "random_id" "vm_sa" { keepers = { vm_hostname = var.vm_hostname } @@ -15,227 +27,497 @@ resource "random_id" "vm-sa" { byte_length = 6 } -resource "azurerm_storage_account" "vm-sa" { - count = var.boot_diagnostics ? 1 : 0 - name = "bootdiag${lower(random_id.vm-sa.hex)}" - resource_group_name = data.azurerm_resource_group.vm.name - location = coalesce(var.location, data.azurerm_resource_group.vm.location) - account_tier = element(split("_", var.boot_diagnostics_sa_type), 0) +moved { + from = azurerm_storage_account.vm-sa + to = azurerm_storage_account.vm_sa +} + +resource "azurerm_storage_account" "vm_sa" { + count = var.boot_diagnostics && var.external_boot_diagnostics_storage == null ? 1 : 0 + account_replication_type = element(split("_", var.boot_diagnostics_sa_type), 1) + account_tier = element(split("_", var.boot_diagnostics_sa_type), 0) + location = local.location + name = "bootdiag${lower(random_id.vm_sa.hex)}" + resource_group_name = var.resource_group_name tags = var.tags } -resource "azurerm_virtual_machine" "vm-linux" { - count = ! contains(list(var.vm_os_simple, var.vm_os_offer), "WindowsServer") && ! var.is_windows_image ? var.nb_instances : 0 - name = "${var.vm_hostname}-vmLinux-${count.index}" - resource_group_name = data.azurerm_resource_group.vm.name - location = coalesce(var.location, data.azurerm_resource_group.vm.location) - availability_set_id = azurerm_availability_set.vm.id - vm_size = var.vm_size - network_interface_ids = [element(azurerm_network_interface.vm.*.id, count.index)] - delete_os_disk_on_termination = var.delete_os_disk_on_termination - - dynamic identity { - for_each = length(var.identity_ids) == 0 && var.identity_type == "SystemAssigned" ? [var.identity_type] : [] - content { - type = var.identity_type - } - } +moved { + from = azurerm_virtual_machine.vm-linux + to = azurerm_virtual_machine.vm_linux +} - dynamic identity { - for_each = length(var.identity_ids) > 0 || var.identity_type == "UserAssigned" ? [var.identity_type] : [] - content { - type = var.identity_type - identity_ids = length(var.identity_ids) > 0 ? var.identity_ids : [] - } - } +resource "azurerm_virtual_machine" "vm_linux" { + count = !local.is_windows ? var.nb_instances : 0 - storage_image_reference { - id = var.vm_os_id - publisher = var.vm_os_id == "" ? coalesce(var.vm_os_publisher, module.os.calculated_value_os_publisher) : "" - offer = var.vm_os_id == "" ? coalesce(var.vm_os_offer, module.os.calculated_value_os_offer) : "" - sku = var.vm_os_id == "" ? coalesce(var.vm_os_sku, module.os.calculated_value_os_sku) : "" - version = var.vm_os_id == "" ? var.vm_os_version : "" - } + location = local.location + name = replace(replace(var.name_template_vm_linux, "$${vm_hostname}", var.vm_hostname), "$${host_number}", count.index) + network_interface_ids = [element(azurerm_network_interface.vm[*].id, count.index)] + resource_group_name = var.resource_group_name + vm_size = var.vm_size + availability_set_id = try(azurerm_availability_set.vm[0].id, null) + delete_data_disks_on_termination = var.delete_data_disks_on_termination + delete_os_disk_on_termination = var.delete_os_disk_on_termination + tags = var.tags + zones = var.zone == null ? null : [var.zone] storage_os_disk { - name = "osdisk-${var.vm_hostname}-${count.index}" create_option = "FromImage" + name = replace(replace(var.name_template_vm_linux_os_disk, "$${vm_hostname}", var.vm_hostname), "$${host_number}", count.index) caching = "ReadWrite" + disk_size_gb = var.storage_os_disk_size_gb managed_disk_type = var.storage_account_type } + boot_diagnostics { + enabled = var.boot_diagnostics + storage_uri = var.boot_diagnostics ? try(var.external_boot_diagnostics_storage.uri, join(",", azurerm_storage_account.vm_sa[*].primary_blob_endpoint)) : "" + } + dynamic "identity" { + for_each = length(var.identity_ids) == 0 && var.identity_type == "SystemAssigned" ? [var.identity_type] : [] - dynamic storage_data_disk { - for_each = range(var.nb_data_disk) content { - name = "${var.vm_hostname}-datadisk-${count.index}-${storage_data_disk.value}" - create_option = "Empty" - lun = storage_data_disk.value - disk_size_gb = var.data_disk_size_gb - managed_disk_type = var.data_sa_type + type = var.identity_type } } + dynamic "identity" { + for_each = length(var.identity_ids) > 0 || var.identity_type == "UserAssigned" ? [var.identity_type] : [] + content { + type = var.identity_type + identity_ids = length(var.identity_ids) > 0 ? var.identity_ids : [] + } + } os_profile { - computer_name = "${var.vm_hostname}-${count.index}" admin_username = var.admin_username + computer_name = "${var.vm_hostname}-${count.index}" admin_password = var.admin_password custom_data = var.custom_data } - os_profile_linux_config { disable_password_authentication = var.enable_ssh_key - dynamic ssh_keys { - for_each = var.enable_ssh_key ? [var.ssh_key] : [] + dynamic "ssh_keys" { + for_each = var.enable_ssh_key ? local.ssh_keys : [] + content { + key_data = file(ssh_keys.value) + path = "/home/${var.admin_username}/.ssh/authorized_keys" + } + } + dynamic "ssh_keys" { + for_each = var.enable_ssh_key ? var.ssh_key_values : [] + + content { + key_data = ssh_keys.value path = "/home/${var.admin_username}/.ssh/authorized_keys" - key_data = file(var.ssh_key) } } } + dynamic "os_profile_secrets" { + for_each = var.os_profile_secrets - tags = var.tags + content { + source_vault_id = os_profile_secrets.value["source_vault_id"] - boot_diagnostics { - enabled = var.boot_diagnostics - storage_uri = var.boot_diagnostics ? join(",", azurerm_storage_account.vm-sa.*.primary_blob_endpoint) : "" + vault_certificates { + certificate_url = os_profile_secrets.value["certificate_url"] + } + } } -} - -resource "azurerm_virtual_machine" "vm-windows" { - count = (var.is_windows_image || contains(list(var.vm_os_simple, var.vm_os_offer), "WindowsServer")) ? var.nb_instances : 0 - name = "${var.vm_hostname}-vmWindows-${count.index}" - resource_group_name = data.azurerm_resource_group.vm.name - location = coalesce(var.location, data.azurerm_resource_group.vm.location) - availability_set_id = azurerm_availability_set.vm.id - vm_size = var.vm_size - network_interface_ids = [element(azurerm_network_interface.vm.*.id, count.index)] - delete_os_disk_on_termination = var.delete_os_disk_on_termination - license_type = var.license_type + dynamic "plan" { + for_each = var.is_marketplace_image ? ["plan"] : [] - dynamic identity { - for_each = length(var.identity_ids) == 0 && var.identity_type == "SystemAssigned" ? [var.identity_type] : [] content { - type = var.identity_type + name = var.vm_os_sku + product = var.vm_os_offer + publisher = var.vm_os_publisher } } + dynamic "storage_data_disk" { + for_each = local.nested_data_disk_list - dynamic identity { - for_each = length(var.identity_ids) > 0 || var.identity_type == "UserAssigned" ? [var.identity_type] : [] content { - type = var.identity_type - identity_ids = length(var.identity_ids) > 0 ? var.identity_ids : [] + create_option = "Empty" + lun = storage_data_disk.value + name = replace(replace(replace(var.name_template_data_disk, "$${vm_hostname}", var.vm_hostname), "$${host_number}", count.index), "$${data_disk_number}", storage_data_disk.value) + disk_size_gb = var.data_disk_size_gb + managed_disk_type = var.data_sa_type } } + dynamic "storage_data_disk" { + for_each = local.nested_extra_data_disk_list + content { + create_option = "Empty" + lun = storage_data_disk.key + var.nb_data_disk + name = replace(replace(replace(var.name_template_extra_disk, "$${vm_hostname}", var.vm_hostname), "$${host_number}", count.index), "$${extra_disk_name}", storage_data_disk.value.name) + disk_size_gb = storage_data_disk.value.size + managed_disk_type = var.data_sa_type + } + } storage_image_reference { id = var.vm_os_id - publisher = var.vm_os_id == "" ? coalesce(var.vm_os_publisher, module.os.calculated_value_os_publisher) : "" offer = var.vm_os_id == "" ? coalesce(var.vm_os_offer, module.os.calculated_value_os_offer) : "" + publisher = var.vm_os_id == "" ? coalesce(var.vm_os_publisher, module.os.calculated_value_os_publisher) : "" sku = var.vm_os_id == "" ? coalesce(var.vm_os_sku, module.os.calculated_value_os_sku) : "" version = var.vm_os_id == "" ? var.vm_os_version : "" } + lifecycle { + precondition { + condition = !var.is_marketplace_image || (var.vm_os_offer != null && var.vm_os_publisher != null && var.vm_os_sku != null) + error_message = "`var.vm_os_offer`, `vm_os_publisher` and `var.vm_os_sku` are required when `var.is_marketplace_image` is `true`." + } + precondition { + condition = var.nested_data_disks || var.delete_data_disks_on_termination != true + error_message = "`var.nested_data_disks` must be `true` when `var.delete_data_disks_on_termination` is `true`, because when you declare data disks via separate managed disk resource, you might want to preserve the data while recreating the vm instance." + } + } +} + +moved { + from = azurerm_virtual_machine.vm-windows + to = azurerm_virtual_machine.vm_windows +} + +resource "azurerm_virtual_machine" "vm_windows" { + count = local.is_windows ? var.nb_instances : 0 + + location = local.location + name = replace(replace(var.name_template_vm_windows, "$${vm_hostname}", var.vm_hostname), "$${host_number}", count.index) + network_interface_ids = [element(azurerm_network_interface.vm[*].id, count.index)] + resource_group_name = var.resource_group_name + vm_size = var.vm_size + availability_set_id = try(azurerm_availability_set.vm[0].id, null) + delete_os_disk_on_termination = var.delete_os_disk_on_termination + license_type = var.license_type + tags = var.tags + zones = var.zone == null ? null : [var.zone] + storage_os_disk { - name = "${var.vm_hostname}-osdisk-${count.index}" create_option = "FromImage" + name = replace(replace(var.name_template_vm_windows_os_disk, "$${vm_hostname}", var.vm_hostname), "$${host_number}", count.index) caching = "ReadWrite" + disk_size_gb = var.storage_os_disk_size_gb managed_disk_type = var.storage_account_type } + boot_diagnostics { + enabled = var.boot_diagnostics + storage_uri = var.boot_diagnostics ? join(",", azurerm_storage_account.vm_sa[*].primary_blob_endpoint) : "" + } + dynamic "identity" { + for_each = length(var.identity_ids) == 0 && var.identity_type == "SystemAssigned" ? [var.identity_type] : [] - dynamic storage_data_disk { - for_each = range(var.nb_data_disk) content { - name = "${var.vm_hostname}-datadisk-${count.index}-${storage_data_disk.value}" - create_option = "Empty" - lun = storage_data_disk.value - disk_size_gb = var.data_disk_size_gb - managed_disk_type = var.data_sa_type + type = var.identity_type } } + dynamic "identity" { + for_each = length(var.identity_ids) > 0 || var.identity_type == "UserAssigned" ? [var.identity_type] : [] + content { + type = var.identity_type + identity_ids = length(var.identity_ids) > 0 ? var.identity_ids : [] + } + } os_profile { - computer_name = "${var.vm_hostname}-${count.index}" admin_username = var.admin_username + computer_name = "${var.vm_hostname}-${count.index}" admin_password = var.admin_password } + dynamic "os_profile_secrets" { + for_each = var.os_profile_secrets - tags = var.tags + content { + source_vault_id = os_profile_secrets.value["source_vault_id"] + vault_certificates { + certificate_url = os_profile_secrets.value["certificate_url"] + certificate_store = os_profile_secrets.value["certificate_store"] + } + } + } os_profile_windows_config { provision_vm_agent = true } + dynamic "plan" { + for_each = var.is_marketplace_image ? ["plan"] : [] - boot_diagnostics { - enabled = var.boot_diagnostics - storage_uri = var.boot_diagnostics ? join(",", azurerm_storage_account.vm-sa.*.primary_blob_endpoint) : "" + content { + name = var.vm_os_sku + product = var.vm_os_offer + publisher = var.vm_os_publisher + } + } + dynamic "storage_data_disk" { + for_each = local.nested_data_disk_list + + content { + create_option = "Empty" + lun = storage_data_disk.value + name = replace(replace(replace(var.name_template_data_disk, "$${vm_hostname}", var.vm_hostname), "$${host_number}", count.index), "$${data_disk_number}", storage_data_disk.value) + disk_size_gb = var.data_disk_size_gb + managed_disk_type = var.data_sa_type + } + } + dynamic "storage_data_disk" { + for_each = local.nested_extra_data_disk_list + + content { + create_option = "Empty" + lun = storage_data_disk.key + var.nb_data_disk + name = replace(replace(replace(var.name_template_extra_disk, "$${vm_hostname}", var.vm_hostname), "$${host_number}", count.index), "$${extra_disk_name}", storage_data_disk.value.name) + disk_size_gb = storage_data_disk.value.size + managed_disk_type = var.data_sa_type + } + } + storage_image_reference { + id = var.vm_os_id + offer = var.vm_os_id == "" ? coalesce(var.vm_os_offer, module.os.calculated_value_os_offer) : "" + publisher = var.vm_os_id == "" ? coalesce(var.vm_os_publisher, module.os.calculated_value_os_publisher) : "" + sku = var.vm_os_id == "" ? coalesce(var.vm_os_sku, module.os.calculated_value_os_sku) : "" + version = var.vm_os_id == "" ? var.vm_os_version : "" + } + + lifecycle { + precondition { + condition = !var.is_marketplace_image || (var.vm_os_offer != null && var.vm_os_publisher != null && var.vm_os_sku != null) + error_message = "`var.vm_os_offer`, `vm_os_publisher` and `var.vm_os_sku` are required when `var.is_marketplace_image` is `true`." + } + precondition { + condition = var.nested_data_disks || var.delete_data_disks_on_termination != true + error_message = "`var.nested_data_disks` must be `true` when `var.delete_data_disks_on_termination` is `true`, because when you declare data disks via separate managed disk resource, you might want to preserve the data while recreating the vm instance." + } } } +resource "azurerm_managed_disk" "vm_data_disk" { + for_each = local.data_disk_map + + create_option = "Empty" + location = local.location + name = each.value.name + resource_group_name = var.resource_group_name + storage_account_type = var.data_sa_type + disk_encryption_set_id = var.managed_data_disk_encryption_set_id + disk_size_gb = var.data_disk_size_gb + tags = var.tags +} + +resource "azurerm_virtual_machine_data_disk_attachment" "vm_data_disk_attachments_linux" { + for_each = local.data_disk_map_linux + + caching = "ReadWrite" + lun = each.value.disk_number + managed_disk_id = azurerm_managed_disk.vm_data_disk[each.key].id + virtual_machine_id = azurerm_virtual_machine.vm_linux[each.value.host_number].id +} + +resource "azurerm_virtual_machine_data_disk_attachment" "vm_data_disk_attachments_windows" { + for_each = local.data_disk_map_windows + + caching = "ReadWrite" + lun = each.value.disk_number + managed_disk_id = azurerm_managed_disk.vm_data_disk[each.key].id + virtual_machine_id = azurerm_virtual_machine.vm_windows[each.value.host_number].id +} + +resource "azurerm_managed_disk" "vm_extra_disk" { + for_each = local.extra_disk_map + + create_option = "Empty" + location = local.location + name = each.value.name + resource_group_name = var.resource_group_name + storage_account_type = var.data_sa_type + disk_encryption_set_id = var.managed_data_disk_encryption_set_id + disk_size_gb = each.value.disk_size + tags = var.tags +} + +resource "azurerm_virtual_machine_data_disk_attachment" "vm_extra_disk_attachments_linux" { + for_each = local.extra_disk_map_linux + + caching = "ReadWrite" + lun = var.nb_data_disk + each.value.disk_number + managed_disk_id = azurerm_managed_disk.vm_extra_disk[each.key].id + virtual_machine_id = azurerm_virtual_machine.vm_linux[each.value.host_number].id +} + +resource "azurerm_virtual_machine_data_disk_attachment" "vm_extra_disk_attachments_windows" { + for_each = local.extra_disk_map_windows + + caching = "ReadWrite" + lun = var.nb_data_disk + each.value.disk_number + managed_disk_id = azurerm_managed_disk.vm_extra_disk[each.key].id + virtual_machine_id = azurerm_virtual_machine.vm_windows[each.value.host_number].id +} + resource "azurerm_availability_set" "vm" { - name = "${var.vm_hostname}-avset" - resource_group_name = data.azurerm_resource_group.vm.name - location = coalesce(var.location, data.azurerm_resource_group.vm.location) - platform_fault_domain_count = 2 - platform_update_domain_count = 2 + count = (var.availability_set_enabled && (var.zone == null)) ? 1 : 0 + + location = local.location + name = replace(var.name_template_availability_set, "$${vm_hostname}", var.vm_hostname) + resource_group_name = var.resource_group_name managed = true + platform_fault_domain_count = var.as_platform_fault_domain_count + platform_update_domain_count = var.as_platform_update_domain_count tags = var.tags } resource "azurerm_public_ip" "vm" { - count = var.nb_public_ip - name = "${var.vm_hostname}-pip-${count.index}" - resource_group_name = data.azurerm_resource_group.vm.name - location = coalesce(var.location, data.azurerm_resource_group.vm.location) + count = var.nb_public_ip + allocation_method = var.allocation_method + location = local.location + name = replace(replace(var.name_template_public_ip, "$${vm_hostname}", var.vm_hostname), "$${ip_number}", count.index) + resource_group_name = var.resource_group_name domain_name_label = element(var.public_ip_dns, count.index) + sku = var.public_ip_sku tags = var.tags + zones = var.zone == null ? null : [var.zone] + + # To solve issue [#107](https://github.com/Azure/terraform-azurerm-compute/issues/107) we add such block to make `azurerm_network_interface.vm`'s update happen first. + # Issue #107's root cause is Terraform will try to execute deletion before update, once we tried to delete the public ip, it is still attached on the network interface. + # Declare this `create_before_destroy` will defer this public ip resource's deletion after creation and update so we can fix the issue. + lifecycle { + create_before_destroy = true + } +} + +# Dynamic public ip address will be got after it's assigned to a vm +data "azurerm_public_ip" "vm" { + count = var.nb_public_ip + + name = azurerm_public_ip.vm[count.index].name + resource_group_name = var.resource_group_name + + depends_on = [azurerm_virtual_machine.vm_linux, azurerm_virtual_machine.vm_windows] +} + +moved { + from = azurerm_network_security_group.vm + to = azurerm_network_security_group.vm[0] } resource "azurerm_network_security_group" "vm" { - name = "${var.vm_hostname}-nsg" - resource_group_name = data.azurerm_resource_group.vm.name - location = coalesce(var.location, data.azurerm_resource_group.vm.location) + count = var.network_security_group == null ? 1 : 0 - tags = var.tags + location = local.location + name = replace(var.name_template_network_security_group, "$${vm_hostname}", var.vm_hostname) + resource_group_name = var.resource_group_name + tags = var.tags +} + +locals { + network_security_group_id = var.network_security_group == null ? azurerm_network_security_group.vm[0].id : var.network_security_group.id } resource "azurerm_network_security_rule" "vm" { - count = var.remote_port != "" ? 1 : 0 + count = var.network_security_group == null && var.remote_port != "" ? 1 : 0 + + access = "Allow" + direction = "Inbound" name = "allow_remote_${coalesce(var.remote_port, module.os.calculated_remote_port)}_in_all" - resource_group_name = data.azurerm_resource_group.vm.name - description = "Allow remote protocol in from all locations" + network_security_group_name = azurerm_network_security_group.vm[0].name priority = 101 - direction = "Inbound" - access = "Allow" protocol = "Tcp" - source_port_range = "*" + resource_group_name = var.resource_group_name + description = "Allow remote protocol in from all locations" + destination_address_prefix = "*" destination_port_range = coalesce(var.remote_port, module.os.calculated_remote_port) source_address_prefixes = var.source_address_prefixes - destination_address_prefix = "*" - network_security_group_name = azurerm_network_security_group.vm.name + source_port_range = "*" } resource "azurerm_network_interface" "vm" { - count = var.nb_instances - name = "${var.vm_hostname}-nic-${count.index}" - resource_group_name = data.azurerm_resource_group.vm.name - location = coalesce(var.location, data.azurerm_resource_group.vm.location) + count = var.nb_instances + + location = local.location + name = replace(replace(var.name_template_network_interface, "$${vm_hostname}", var.vm_hostname), "$${host_number}", count.index) + resource_group_name = var.resource_group_name enable_accelerated_networking = var.enable_accelerated_networking + tags = var.tags ip_configuration { name = "${var.vm_hostname}-ip-${count.index}" - subnet_id = var.vnet_subnet_id private_ip_address_allocation = "Dynamic" - public_ip_address_id = length(azurerm_public_ip.vm.*.id) > 0 ? element(concat(azurerm_public_ip.vm.*.id, list("")), count.index) : "" + public_ip_address_id = length(azurerm_public_ip.vm[*].id) > 0 ? element(concat(azurerm_public_ip.vm[*].id, tolist([ + "" + ])), count.index) : "" + subnet_id = var.vnet_subnet_id } - - tags = var.tags } resource "azurerm_network_interface_security_group_association" "test" { - count = var.nb_instances + count = var.nb_instances + network_interface_id = azurerm_network_interface.vm[count.index].id - network_security_group_id = azurerm_network_security_group.vm.id + network_security_group_id = local.network_security_group_id } + +resource "azurerm_virtual_machine_extension" "extension" { + count = var.vm_extension == null ? 0 : var.nb_instances + + name = var.vm_extension.name + publisher = var.vm_extension.publisher + type = var.vm_extension.type + type_handler_version = var.vm_extension.type_handler_version + virtual_machine_id = local.is_windows ? azurerm_virtual_machine.vm_windows[count.index].id : azurerm_virtual_machine.vm_linux[count.index].id + auto_upgrade_minor_version = var.vm_extension.auto_upgrade_minor_version + automatic_upgrade_enabled = var.vm_extension.automatic_upgrade_enabled + failure_suppression_enabled = var.vm_extension.failure_suppression_enabled + protected_settings = var.vm_extension.protected_settings + settings = var.vm_extension.settings + tags = var.tags + + dynamic "protected_settings_from_key_vault" { + for_each = var.vm_extension.protected_settings_from_key_vault == null ? [] : ["protected_settings_from_key_vault"] + + content { + secret_url = var.vm_extension.protected_settings_from_key_vault.secret_url + source_vault_id = var.vm_extension.protected_settings_from_key_vault.source_vault_id + } + } + + lifecycle { + precondition { + condition = length(var.vm_extensions) == 0 + error_message = "`vm_extensions` cannot be used along with `vm_extension`." + } + } +} + +resource "azurerm_virtual_machine_extension" "extensions" { + # The `sensitive` inside `nonsensitive` is a workaround for https://github.com/terraform-linters/tflint-ruleset-azurerm/issues/229 + for_each = nonsensitive(sensitive(local.vm_extensions)) + + name = each.value.value.name + publisher = each.value.value.publisher + type = each.value.value.type + type_handler_version = each.value.value.type_handler_version + virtual_machine_id = local.is_windows ? azurerm_virtual_machine.vm_windows[each.value.index].id : azurerm_virtual_machine.vm_linux[each.value.index].id + auto_upgrade_minor_version = each.value.value.auto_upgrade_minor_version + automatic_upgrade_enabled = each.value.value.automatic_upgrade_enabled + failure_suppression_enabled = each.value.value.failure_suppression_enabled + protected_settings = each.value.value.protected_settings + settings = each.value.value.settings + tags = var.tags + + dynamic "protected_settings_from_key_vault" { + for_each = each.value.value.protected_settings_from_key_vault == null ? [] : ["protected_settings_from_key_vault"] + + content { + secret_url = each.value.value.protected_settings_from_key_vault.secret_url + source_vault_id = each.value.value.protected_settings_from_key_vault.source_vault_id + } + } + + lifecycle { + precondition { + condition = var.vm_extension == null + error_message = "`vm_extensions` cannot be used along with `vm_extension`." + } + } +} \ No newline at end of file diff --git a/os/outputs.tf b/os/outputs.tf index df1687b..84f6292 100644 --- a/os/outputs.tf +++ b/os/outputs.tf @@ -1,15 +1,15 @@ output "calculated_value_os_publisher" { - value = "${element(split(",", lookup(var.standard_os, var.vm_os_simple, "")), 0)}" + value = element(split(",", lookup(var.standard_os, var.vm_os_simple, "")), 0) } output "calculated_value_os_offer" { - value = "${element(split(",", lookup(var.standard_os, var.vm_os_simple, "")), 1)}" + value = element(split(",", lookup(var.standard_os, var.vm_os_simple, "")), 1) } output "calculated_value_os_sku" { - value = "${element(split(",", lookup(var.standard_os, var.vm_os_simple, "")), 2)}" + value = element(split(",", lookup(var.standard_os, var.vm_os_simple, "")), 2) } output "calculated_remote_port" { - value = "${element(split(",", lookup(var.standard_os, var.vm_os_simple, "")), 0) == "MicrosoftWindowsServer" ? 3389 : 22}" + value = element(split(",", lookup(var.standard_os, var.vm_os_simple, "")), 0) == "MicrosoftWindowsServer" ? 3389 : 22 } diff --git a/outputs.tf b/outputs.tf index f15a08f..3d7d1e4 100644 --- a/outputs.tf +++ b/outputs.tf @@ -1,54 +1,62 @@ -output "vm_ids" { - description = "Virtual machine ids created." - value = concat(azurerm_virtual_machine.vm-windows.*.id, azurerm_virtual_machine.vm-linux.*.id) -} - -output "network_security_group_id" { - description = "id of the security group provisioned" - value = azurerm_network_security_group.vm.id -} - -output "network_security_group_name" { - description = "name of the security group provisioned" - value = azurerm_network_security_group.vm.name +output "availability_set_id" { + description = "Id of the availability set where the vms are provisioned. If `var.zones` is set, this output will return empty string." + value = join("", azurerm_availability_set.vm[*].id) } output "network_interface_ids" { description = "ids of the vm nics provisoned." - value = azurerm_network_interface.vm.*.id + value = azurerm_network_interface.vm[*].id } output "network_interface_private_ip" { description = "private ip addresses of the vm nics" - value = azurerm_network_interface.vm.*.private_ip_address + value = azurerm_network_interface.vm[*].private_ip_address } -output "public_ip_id" { - description = "id of the public ip address provisoned." - value = azurerm_public_ip.vm.*.id +output "network_security_group_id" { + description = "id of the security group provisioned" + value = local.network_security_group_id +} + +output "network_security_group_name" { + description = "name of the security group provisioned, empty if no security group was created." + value = join("", concat(azurerm_network_security_group.vm[*].name, [""])) } output "public_ip_address" { description = "The actual ip address allocated for the resource." - value = azurerm_public_ip.vm.*.ip_address + value = data.azurerm_public_ip.vm[*].ip_address } output "public_ip_dns_name" { description = "fqdn to connect to the first vm provisioned." - value = azurerm_public_ip.vm.*.fqdn + value = azurerm_public_ip.vm[*].fqdn } -output "availability_set_id" { - description = "id of the availability set where the vms are provisioned." - value = azurerm_availability_set.vm.id -} - -output "vm_zones" { - description = "map with key `Virtual Machine Id`, value `list of the Availability Zone` which the Virtual Machine should be allocated in." - value = zipmap(concat(azurerm_virtual_machine.vm-windows.*.id, azurerm_virtual_machine.vm-linux.*.id), concat(azurerm_virtual_machine.vm-windows.*.zones, azurerm_virtual_machine.vm-linux.*.zones)) +output "public_ip_id" { + description = "id of the public ip address provisoned." + value = azurerm_public_ip.vm[*].id } output "vm_identity" { description = "map with key `Virtual Machine Id`, value `list of identity` created for the Virtual Machine." - value = zipmap(concat(azurerm_virtual_machine.vm-windows.*.id, azurerm_virtual_machine.vm-linux.*.id), concat(azurerm_virtual_machine.vm-windows.*.identity, azurerm_virtual_machine.vm-linux.*.identity)) + value = zipmap(concat([for m in azurerm_virtual_machine.vm_windows : m.id], [for m in azurerm_virtual_machine.vm_linux : m.id]), concat(azurerm_virtual_machine.vm_windows[*].identity, azurerm_virtual_machine.vm_linux[*].identity)) +} + +output "vm_ids" { + description = "Virtual machine ids created." + value = concat(azurerm_virtual_machine.vm_windows[*].id, azurerm_virtual_machine.vm_linux[*].id) +} + +output "vm_names" { + description = "Virtual machine names created." + value = { + linux = azurerm_virtual_machine.vm_linux[*].name + windows = azurerm_virtual_machine.vm_windows[*].name + } } + +output "vm_zones" { + description = "map with key `Virtual Machine Id`, value `list of the Availability Zone` which the Virtual Machine should be allocated in." + value = zipmap(concat(azurerm_virtual_machine.vm_windows[*].id, azurerm_virtual_machine.vm_linux[*].id), concat(azurerm_virtual_machine.vm_windows[*].zones, azurerm_virtual_machine.vm_linux[*].zones)) +} \ No newline at end of file diff --git a/test/e2e/terraform_compute_test.go b/test/e2e/terraform_compute_test.go new file mode 100644 index 0000000..a73c51d --- /dev/null +++ b/test/e2e/terraform_compute_test.go @@ -0,0 +1,43 @@ +package e2e + +import ( + "os" + "testing" + + test_helper "github.com/Azure/terraform-module-test-helper" + "github.com/gruntwork-io/terratest/modules/terraform" + "github.com/stretchr/testify/assert" +) + +const ipRegex = `^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$` + +func TestExamplesComplete(t *testing.T) { + vars := make(map[string]interface{}) + managedIdentityId := os.Getenv("MSI_ID") + if managedIdentityId != "" { + _ = os.Setenv("TF_VAR_managed_identity_principal_id", managedIdentityId) + } + test_helper.RunE2ETest(t, "../../", "examples/complete", terraform.Options{ + Upgrade: true, + Vars: vars, + }, func(t *testing.T, output test_helper.TerraformOutput) { + assertVmIpAddresses(t, "debian_ip_address", output) + assertVmIpAddresses(t, "ubuntu_ip_address", output) + assertVmIpAddresses(t, "windows_ip_address", output) + }) +} + +func TestExamplesExtensions(t *testing.T) { + test_helper.RunE2ETest(t, "../../", "examples/extensions", terraform.Options{ + Upgrade: true, + }, nil) +} + +func assertVmIpAddresses(t *testing.T, outputName string, output test_helper.TerraformOutput) { + o, ok := output[outputName] + assert.True(t, ok) + addresses, ok := o.([]interface{}) + assert.True(t, ok) + assert.Equal(t, 1, len(addresses)) + assert.Regexp(t, ipRegex, addresses[0].(string)) +} diff --git a/test/fixture/main.tf b/test/fixture/main.tf deleted file mode 100644 index 3d65de6..0000000 --- a/test/fixture/main.tf +++ /dev/null @@ -1,102 +0,0 @@ -provider "azurerm" { - features {} -} - -resource "random_id" "ip_dns" { - byte_length = 4 -} - -resource "azurerm_resource_group" "test" { - name = "host${random_id.ip_dns.hex}-rg" - location = var.location -} - -resource "azurerm_virtual_network" "vnet" { - name = "host${random_id.ip_dns.hex}-vn" - location = var.location_alt - address_space = ["10.0.0.0/16"] - resource_group_name = azurerm_resource_group.test.name -} - -resource "azurerm_subnet" "subnet1" { - name = "host${random_id.ip_dns.hex}-sn-1" - virtual_network_name = azurerm_virtual_network.vnet.name - resource_group_name = azurerm_resource_group.test.name - address_prefixes = ["10.0.1.0/24"] -} - -resource "azurerm_subnet" "subnet2" { - name = "host${random_id.ip_dns.hex}-sn-2" - virtual_network_name = azurerm_virtual_network.vnet.name - resource_group_name = azurerm_resource_group.test.name - address_prefixes = ["10.0.2.0/24"] -} - -resource "azurerm_subnet" "subnet3" { - name = "host${random_id.ip_dns.hex}-sn-3" - virtual_network_name = azurerm_virtual_network.vnet.name - resource_group_name = azurerm_resource_group.test.name - address_prefixes = ["10.0.3.0/24"] -} - -resource "azurerm_user_assigned_identity" "test" { - resource_group_name = azurerm_resource_group.test.name - location = azurerm_resource_group.test.location - - name = "host${random_id.ip_dns.hex}-id" -} - -module "ubuntuservers" { - source = "../../" - vm_hostname = "${random_id.ip_dns.hex}-u" - resource_group_name = azurerm_resource_group.test.name - location = var.location_alt - admin_username = var.admin_username - admin_password = var.admin_password - vm_os_simple = var.vm_os_simple_1 - public_ip_dns = ["ubuntusimplevmips-${random_id.ip_dns.hex}"] - vnet_subnet_id = azurerm_subnet.subnet1.id - allocation_method = "Static" - enable_accelerated_networking = true - vm_size = "Standard_DS2_V2" - nb_data_disk = 2 - enable_ssh_key = false - identity_type = "UserAssigned" - identity_ids = [azurerm_user_assigned_identity.test.id] - - depends_on = [azurerm_resource_group.test] -} - -module "debianservers" { - source = "../../" - vm_hostname = "${random_id.ip_dns.hex}-d" - resource_group_name = azurerm_resource_group.test.name - location = var.location_alt - admin_username = var.admin_username - admin_password = var.admin_password - custom_data = var.custom_data - vm_os_simple = var.vm_os_simple_2 - public_ip_dns = ["debiansimplevmips-${random_id.ip_dns.hex}"] // change to a unique name per datacenter region - vnet_subnet_id = azurerm_subnet.subnet2.id - allocation_method = "Static" - enable_ssh_key = true - - depends_on = [azurerm_resource_group.test] -} - -module "windowsservers" { - source = "../../" - vm_hostname = "${random_id.ip_dns.hex}-w" // line can be removed if only one VM module per resource group - resource_group_name = azurerm_resource_group.test.name - location = var.location_alt - is_windows_image = true - admin_username = var.admin_username - admin_password = var.admin_password - vm_os_simple = "WindowsServer" - public_ip_dns = ["winsimplevmips-${random_id.ip_dns.hex}"] // change to a unique name per datacenter region - vnet_subnet_id = azurerm_subnet.subnet3.id - license_type = var.license_type - identity_type = var.identity_type - - depends_on = [azurerm_resource_group.test] -} diff --git a/test/fixture/outputs.tf b/test/fixture/outputs.tf deleted file mode 100644 index 749fa7e..0000000 --- a/test/fixture/outputs.tf +++ /dev/null @@ -1,31 +0,0 @@ -output "ubuntu_vm_public_name" { - value = module.ubuntuservers.public_ip_dns_name -} - -output "debian_vm_public_name" { - value = module.debianservers.public_ip_dns_name -} - -output "windows_vm_public_name" { - value = module.windowsservers.public_ip_dns_name -} - -output "ubuntu_ip_address" { - value = module.ubuntuservers.public_ip_address -} - -output "debian_ip_address" { - value = module.debianservers.public_ip_address -} - -output "windows_ip_address" { - value = module.windowsservers.public_ip_address -} - -output "ubuntu_identity_type" { - value = module.ubuntuservers.vm_identity -} - -output "windows_identity_type" { - value = module.windowsservers.vm_identity -} diff --git a/test/fixture/terraform.tfvars b/test/fixture/terraform.tfvars deleted file mode 100644 index 4be88ba..0000000 --- a/test/fixture/terraform.tfvars +++ /dev/null @@ -1,9 +0,0 @@ -location = "eastus" -location_alt = "eastus2" -vm_os_simple_1 = "UbuntuServer" -vm_os_simple_2 = "Debian" -admin_username = "azureuser" -admin_password = "P@ssw0rd12345!" -custom_data = "" -license_type = "Windows_Client" -identity_type = "SystemAssigned" diff --git a/test/fixture/variables.tf b/test/fixture/variables.tf deleted file mode 100644 index f148920..0000000 --- a/test/fixture/variables.tf +++ /dev/null @@ -1,9 +0,0 @@ -variable "location" {} -variable "location_alt" {} -variable "vm_os_simple_1" {} -variable "vm_os_simple_2" {} -variable "admin_username" {} -variable "admin_password" {} -variable "custom_data" {} -variable "license_type" {} -variable "identity_type" {} diff --git a/test/go.mod b/test/go.mod new file mode 100644 index 0000000..b8345b3 --- /dev/null +++ b/test/go.mod @@ -0,0 +1,102 @@ +module github.com/Azure/terraform-azurerm-compute + +go 1.19 + +require ( + github.com/Azure/terraform-module-test-helper v0.9.1 + github.com/gruntwork-io/terratest v0.41.11 + github.com/stretchr/testify v1.8.1 +) + +require ( + cloud.google.com/go v0.104.0 // indirect + cloud.google.com/go/compute v1.10.0 // indirect + cloud.google.com/go/iam v0.5.0 // indirect + cloud.google.com/go/storage v1.27.0 // indirect + github.com/agext/levenshtein v1.2.3 // indirect + github.com/ahmetb/go-linq/v3 v3.2.0 // indirect + github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect + github.com/aws/aws-sdk-go v1.44.122 // indirect + github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect + github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c // indirect + github.com/go-errors/errors v1.0.2-0.20180813162953-d98b870cc4e0 // indirect + github.com/go-logr/logr v0.2.0 // indirect + github.com/go-sql-driver/mysql v1.4.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-github/v42 v42.0.0 // indirect + github.com/google/go-querystring v1.1.0 // indirect + github.com/google/gofuzz v1.1.0 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect + github.com/googleapis/gax-go/v2 v2.6.0 // indirect + github.com/googleapis/gnostic v0.4.1 // indirect + github.com/gruntwork-io/go-commons v0.8.0 // indirect + github.com/hashicorp/errwrap v1.0.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-getter v1.7.0 // indirect + github.com/hashicorp/go-getter/v2 v2.2.0 // indirect + github.com/hashicorp/go-multierror v1.1.0 // indirect + github.com/hashicorp/go-safetemp v1.0.0 // indirect + github.com/hashicorp/go-version v1.6.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/hashicorp/hcl/v2 v2.16.0 // indirect + github.com/hashicorp/terraform-config-inspect v0.0.0-20211115214459-90acf1ca460f // indirect + github.com/hashicorp/terraform-json v0.15.0 // indirect + github.com/imdario/mergo v0.3.11 // indirect + github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/json-iterator/go v1.1.11 // indirect + github.com/klauspost/compress v1.15.11 // indirect + github.com/lonegunmanb/tfmodredirector v0.1.0 // indirect + github.com/magodo/hclgrep v0.0.0-20220303061548-1b2b24c7caf6 // indirect + github.com/mattn/go-zglob v0.0.2-0.20190814121620-e3c945676326 // indirect + github.com/minamijoyo/hcledit v0.2.6 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/go-testing-interface v1.14.1 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pquerna/otp v1.2.0 // indirect + github.com/r3labs/diff/v3 v3.0.1 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/spf13/afero v1.9.3 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/tmccombs/hcl2json v0.3.3 // indirect + github.com/ulikunitz/xz v0.5.10 // indirect + github.com/urfave/cli v1.22.2 // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/zclconf/go-cty v1.12.1 // indirect + go.opencensus.io v0.23.0 // indirect + golang.org/x/crypto v0.0.0-20220517005047-85d78b3ac167 // indirect + golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect + golang.org/x/net v0.7.0 // indirect + golang.org/x/oauth2 v0.1.0 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/term v0.5.0 // indirect + golang.org/x/text v0.7.0 // indirect + golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect + google.golang.org/api v0.100.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71 // indirect + google.golang.org/grpc v1.50.1 // indirect + google.golang.org/protobuf v1.28.1 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/api v0.20.6 // indirect + k8s.io/apimachinery v0.20.6 // indirect + k8s.io/client-go v0.20.6 // indirect + k8s.io/klog/v2 v2.4.0 // indirect + k8s.io/utils v0.0.0-20201110183641-67b214c5f920 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.0.3 // indirect + sigs.k8s.io/yaml v1.2.0 // indirect +) diff --git a/test/terraform_ssh_example_test.go b/test/terraform_ssh_example_test.go deleted file mode 100644 index e03b7af..0000000 --- a/test/terraform_ssh_example_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package test - -import ( - "testing" - "github.com/gruntwork-io/terratest/modules/terraform" - "github.com/gruntwork-io/terratest/modules/test-structure" -) - -func TestTerraformSshExample(t *testing.T) { - t.Parallel() - - exampleFolder := "./fixture" - - // At the end of the test, run `terraform destroy` to clean up any resources that were created - defer test_structure.RunTestStage(t, "teardown", func() { - terraformOptions := test_structure.LoadTerraformOptions(t, exampleFolder) - terraform.Destroy(t, terraformOptions) - }) - - // Deploy the example - test_structure.RunTestStage(t, "setup", func() { - terraformOptions := configureTerraformOptions(t, exampleFolder) - - // Save the options so later test stages can use them - test_structure.SaveTerraformOptions(t, exampleFolder, terraformOptions) - - // This will run `terraform init` and `terraform apply` and fail the test if there are any errors - terraform.InitAndApply(t, terraformOptions) - }) - - // It has ever been planned to test the VM could be accessed from the public through SSH, - // however currently this connection is constrained because the testing VM in CI is within the Microsoft internal environment and the public cannot access it. - // So skip this test. -} - -func configureTerraformOptions(t *testing.T, exampleFolder string) *terraform.Options { - - terraformOptions := &terraform.Options{ - // The path to where our Terraform code is located - TerraformDir: exampleFolder, - - // Variables to pass to our Terraform code using -var options - Vars: map[string]interface{}{}, - } - - return terraformOptions -} - diff --git a/test/unit/terraform_compute_test.go b/test/unit/terraform_compute_test.go new file mode 100644 index 0000000..d9709df --- /dev/null +++ b/test/unit/terraform_compute_test.go @@ -0,0 +1,220 @@ +package unit + +import ( + "fmt" + "strconv" + "strings" + "testing" + + test_helper "github.com/Azure/terraform-module-test-helper" + "github.com/gruntwork-io/terratest/modules/terraform" + "github.com/stretchr/testify/require" +) + +func Test_ZeroInstancesShouldGenerateEmptyExtenions(t *testing.T) { + vars := map[string]interface{}{ + "nb_instances": 0, + } + test_helper.RunE2ETest(t, "../../", "unit-fixture", terraform.Options{ + Upgrade: true, + Vars: vars, + }, func(t *testing.T, output test_helper.TerraformOutput) { + extensions, ok := output["vm_extensions"].(map[string]interface{}) + require.True(t, ok) + require.Empty(t, extensions) + }) +} + +func Test_EmptyExtensionsShouldGenerateEmptyExtenions(t *testing.T) { + vars := map[string]interface{}{ + "nb_instances": 1, + "vm_extensions": []interface{}{}, + } + test_helper.RunE2ETest(t, "../../", "unit-fixture", terraform.Options{ + Upgrade: true, + Vars: vars, + }, func(t *testing.T, output test_helper.TerraformOutput) { + extensions, ok := output["vm_extensions"].(map[string]interface{}) + require.True(t, ok) + require.Empty(t, extensions) + }) +} + +func Test_OneInstances(t *testing.T) { + vars := map[string]interface{}{ + "nb_instances": 1, + } + test_helper.RunE2ETest(t, "../../", "unit-fixture", terraform.Options{ + Upgrade: true, + Vars: vars, + }, func(t *testing.T, output test_helper.TerraformOutput) { + extensions, ok := output["vm_extensions"].(map[string]interface{}) + require.True(t, ok) + require.Equal(t, 2, len(extensions)) + require.Contains(t, extensions, "hostname-0") + require.Contains(t, extensions, "AzureMonitorLinuxAgent-0") + for _, v := range extensions { + m := v.(map[string]interface{}) + require.Zero(t, m["index"]) + require.Contains(t, m, "value") + } + }) +} + +func Test_TwoInstances(t *testing.T) { + vars := map[string]interface{}{ + "nb_instances": 2, + } + test_helper.RunE2ETest(t, "../../", "unit-fixture", terraform.Options{ + Upgrade: true, + Vars: vars, + }, func(t *testing.T, output test_helper.TerraformOutput) { + extensions, ok := output["vm_extensions"].(map[string]interface{}) + require.True(t, ok) + require.Equal(t, 4, len(extensions)) + require.Contains(t, extensions, "hostname-0") + require.Contains(t, extensions, "AzureMonitorLinuxAgent-0") + require.Contains(t, extensions, "hostname-1") + require.Contains(t, extensions, "AzureMonitorLinuxAgent-1") + for k, v := range extensions { + index, err := strconv.ParseFloat(strings.Split(k, "-")[1], 64) + require.Nil(t, err) + m := v.(map[string]interface{}) + require.Equal(t, m["index"].(float64), index) + } + }) +} + +func Test_ExtensionVmIdMap(t *testing.T) { + isWindowsImages := []bool{ + false, + true, + } + for _, w := range isWindowsImages { + isWindows := w + t.Run(strconv.FormatBool(isWindows), func(t *testing.T) { + vars := map[string]interface{}{ + "is_windows_image": isWindows, + "nb_instances": 2, + } + test_helper.RunE2ETest(t, "../../", "unit-fixture", terraform.Options{ + Upgrade: true, + Vars: vars, + }, func(t *testing.T, output test_helper.TerraformOutput) { + extensions, ok := output["generated_extensions"].(map[string]interface{}) + require.True(t, ok) + require.Equal(t, 4, len(extensions)) + for k, e := range extensions { + m := e.(map[string]interface{}) + output := m["outputs"].(map[string]interface{}) + vmId := output["virtual_machine_id"].(string) + prefix := "linux-%s" + if isWindows { + prefix = "windows-%s" + } + index := strings.Split(k, "-")[1] + require.Equal(t, fmt.Sprintf(prefix, index), vmId) + } + }) + }) + } +} + +func Test_DefaultOrTrueNestedDataDisksShouldNotCreateAttachmentResource(t *testing.T) { + test_helper.RunE2ETest(t, "../../", "unit-fixture", terraform.Options{ + Vars: map[string]interface{}{ + "nested_data_disks": true, + "nb_instances": 2, + "nb_data_disk": 2, + "extra_disks": `[ + { + name = "lun0" + size = 30 + } +]`, + }, + }, func(t *testing.T, output test_helper.TerraformOutput) { + dataList := output["data_disk_list"].([]any) + require.NotEmpty(t, dataList) + nestedDataDiskList := output["nested_data_disk_list"].([]any) + require.Equal(t, 2, len(nestedDataDiskList)) + extraDataList := output["extra_data_disk_list"].([]any) + require.NotEmpty(t, extraDataList) + nestedExtraDataList := output["nested_extra_data_disk_list"].([]any) + require.NotEmpty(t, nestedExtraDataList) + dataDisks := output["data_disk_map"].(map[string]any) + require.Empty(t, dataDisks) + extraDataDisks := output["extra_disk_map"].(map[string]any) + require.Empty(t, extraDataDisks) + }) +} + +func Test_FalseNestedDataDisksShouldCreateAttachmentResource(t *testing.T) { + test_helper.RunE2ETest(t, "../../", "unit-fixture", terraform.Options{ + Vars: map[string]interface{}{ + "nested_data_disks": false, + "nb_instances": 2, + "nb_data_disk": 2, + "extra_disks": `[ + { + name = "lun0" + size = 30 + } +]`, + }, + }, func(t *testing.T, output test_helper.TerraformOutput) { + dataList := output["data_disk_list"].([]any) + require.NotEmpty(t, dataList) + nestedDataDiskList := output["nested_data_disk_list"].([]any) + require.Empty(t, nestedDataDiskList) + extraDataList := output["extra_data_disk_list"].([]any) + require.NotEmpty(t, extraDataList) + nestedExtraDataList := output["nested_extra_data_disk_list"].([]any) + require.Empty(t, nestedExtraDataList) + dataDisks := output["data_disk_map"].(map[string]any) + require.Equal(t, 4, len(dataDisks)) + extraDataDisks := output["extra_disk_map"].(map[string]any) + require.Equal(t, 2, len(extraDataDisks)) + }) +} + +func Test_DataDisksAttachmentShouldMatchOs(t *testing.T) { + isWindows := []bool{ + false, true, + } + for _, w := range isWindows { + isLinux := !w + t.Run(strconv.FormatBool(isLinux), func(t *testing.T) { + test_helper.RunE2ETest(t, "../../", "unit-fixture", terraform.Options{ + Vars: map[string]interface{}{ + "is_windows_image": !isLinux, + "nested_data_disks": false, + "nb_instances": 2, + "nb_data_disk": 2, + "extra_disks": `[ + { + name = "lun0" + size = 30 + } +]`, + }, + }, func(t *testing.T, output test_helper.TerraformOutput) { + dataDiskMapLinux := output["data_disk_map_linux"].(map[string]any) + dataDiskMapWindows := output["data_disk_map_windows"].(map[string]any) + extraDiskMapLinux := output["extra_disk_map_linux"].(map[string]any) + extraDiskMapWindows := output["extra_disk_map_windows"].(map[string]any) + if isLinux { + require.Empty(t, dataDiskMapWindows) + require.Empty(t, extraDiskMapWindows) + require.NotEmpty(t, dataDiskMapLinux) + require.NotEmpty(t, extraDiskMapLinux) + } else { + require.NotEmpty(t, dataDiskMapWindows) + require.NotEmpty(t, extraDiskMapWindows) + require.Empty(t, dataDiskMapLinux) + require.Empty(t, extraDiskMapLinux) + } + }) + }) + } +} diff --git a/test/upgrade/upgrade_test.go b/test/upgrade/upgrade_test.go new file mode 100644 index 0000000..11217bf --- /dev/null +++ b/test/upgrade/upgrade_test.go @@ -0,0 +1,29 @@ +package upgrade + +import ( + "os" + "testing" + + test_helper "github.com/Azure/terraform-module-test-helper" + "github.com/gruntwork-io/terratest/modules/terraform" +) + +func TestExampleUpgrade_complete(t *testing.T) { + currentRoot, err := test_helper.GetCurrentModuleRootPath() + if err != nil { + t.FailNow() + } + currentMajorVersion, err := test_helper.GetCurrentMajorVersionFromEnv() + if err != nil { + t.FailNow() + } + vars := make(map[string]interface{}) + managedIdentityId := os.Getenv("MSI_ID") + if managedIdentityId != "" { + _ = os.Setenv("TF_VAR_managed_identity_principal_id", managedIdentityId) + } + test_helper.ModuleUpgradeTest(t, "Azure", "terraform-azurerm-compute", "examples/complete", currentRoot, terraform.Options{ + Upgrade: true, + Vars: vars, + }, currentMajorVersion) +} diff --git a/unit-fixture/locals.tf b/unit-fixture/locals.tf new file mode 120000 index 0000000..1b032e6 --- /dev/null +++ b/unit-fixture/locals.tf @@ -0,0 +1 @@ +../locals.tf \ No newline at end of file diff --git a/unit-fixture/main.tf b/unit-fixture/main.tf new file mode 100644 index 0000000..90e5f70 --- /dev/null +++ b/unit-fixture/main.tf @@ -0,0 +1,14 @@ +locals { + windows_vm_ids = [for i in range(var.nb_instances) : "windows-${i}"] + linux_vm_ids = [for i in range(var.nb_instances) : "linux-${i}"] +} + +data "null_data_source" "extensions" { + for_each = nonsensitive(local.vm_extensions) + + inputs = { + virtual_machine_id = (var.is_windows_image || contains(tolist([ + var.vm_os_simple, var.vm_os_offer + ]), "WindowsServer")) ? local.windows_vm_ids[each.value.index] : local.linux_vm_ids[each.value.index] + } +} \ No newline at end of file diff --git a/unit-fixture/outputs.tf b/unit-fixture/outputs.tf new file mode 100644 index 0000000..7e282b1 --- /dev/null +++ b/unit-fixture/outputs.tf @@ -0,0 +1,49 @@ +output "data_disk_list" { + value = local.data_disk_list +} + +output "data_disk_map" { + value = local.data_disk_map +} + +output "data_disk_map_linux" { + value = local.data_disk_map_linux +} + +output "data_disk_map_windows" { + value = local.data_disk_map_windows +} + +output "extra_data_disk_list" { + value = local.extra_disk_list +} + +output "extra_disk_map" { + value = local.extra_disk_map +} + +output "extra_disk_map_linux" { + value = local.extra_disk_map_linux +} + +output "extra_disk_map_windows" { + value = local.extra_disk_map_windows +} + +output "generated_extensions" { + value = data.null_data_source.extensions + sensitive = true +} + +output "nested_data_disk_list" { + value = local.nested_data_disk_list +} + +output "nested_extra_data_disk_list" { + value = local.nested_extra_data_disk_list +} + +output "vm_extensions" { + value = local.vm_extensions + sensitive = true +} \ No newline at end of file diff --git a/unit-fixture/terraform.auto.tfvars b/unit-fixture/terraform.auto.tfvars new file mode 100644 index 0000000..bf0265f --- /dev/null +++ b/unit-fixture/terraform.auto.tfvars @@ -0,0 +1,18 @@ +resource_group_name = "dummy" +vnet_subnet_id = "dummy" +vm_extensions = [ + { + "name" : "hostname" + "publisher" : "Microsoft.Azure.Extensions", + "type" : "CustomScript", + "type_handler_version" : "2.0", + "settings" : "{\"commandToExecute\": \"hostname && uptime\"}", + }, + { + "name" : "AzureMonitorLinuxAgent", + "publisher" : "Microsoft.Azure.Monitor", + "type" : "AzureMonitorLinuxAgent", + "type_handler_version" : "1.21", + "auto_upgrade_minor_version" : true + }, +] \ No newline at end of file diff --git a/unit-fixture/variables.tf b/unit-fixture/variables.tf new file mode 120000 index 0000000..3a65dcc --- /dev/null +++ b/unit-fixture/variables.tf @@ -0,0 +1 @@ +../variables.tf \ No newline at end of file diff --git a/variables.tf b/variables.tf index 238e73a..00691dc 100644 --- a/variables.tf +++ b/variables.tf @@ -3,45 +3,60 @@ variable "resource_group_name" { type = string } -variable "location" { - description = "(Optional) The location in which the resources will be created." - type = string - default = "" -} - variable "vnet_subnet_id" { description = "The subnet id of the virtual network where the virtual machines will reside." type = string } -variable "public_ip_dns" { - description = "Optional globally unique per datacenter region domain name label to apply to each public ip address. e.g. thisvar.varlocation.cloudapp.azure.com where you specify only thisvar here. This is an array of names which will pair up sequentially to the number of public ips defined in var.nb_public_ip. One name or empty string is required for every public ip. If no public ip is desired, then set this to an array with a single empty string." - type = list(string) - default = [null] -} - variable "admin_password" { description = "The admin password to be used on the VMSS that will be deployed. The password must meet the complexity requirements of Azure." type = string default = "" } -variable "ssh_key" { - description = "Path to the public key to be used for ssh access to the VM. Only used with non-Windows vms and can be left as-is even if using Windows vms. If specifying a path to a certification on a Windows machine to provision a linux vm use the / in the path versus backslash. e.g. c:/home/id_rsa.pub." +variable "admin_username" { + description = "The admin username of the VM that will be deployed." type = string - default = "~/.ssh/id_rsa.pub" + default = "azureuser" } -variable "remote_port" { - description = "Remote tcp port to be used for access to the vms created via the nsg applied to the nics." +variable "allocation_method" { + description = "Defines how an IP address is assigned. Options are Static or Dynamic." type = string - default = "" + default = "Dynamic" } -variable "admin_username" { - description = "The admin username of the VM that will be deployed." +# We keep default value as `2`, not `3` as the official since this module used to hard code this argument to `2`. +variable "as_platform_fault_domain_count" { + description = "(Optional) Specifies the number of fault domains that are used. Defaults to `2`. Changing this forces a new resource to be created." + type = number + default = 2 +} + +# We keep default value as `2`, not `5` as the official since this module used to hard code this argument to `2`. +variable "as_platform_update_domain_count" { + description = "(Optional) Specifies the number of update domains that are used. Defaults to `2`. Changing this forces a new resource to be created." + type = number + default = 2 +} + +variable "availability_set_enabled" { + description = "(Optional) Enable or Disable availability set. Default is `true` (enabled)." + type = bool + nullable = false + default = true +} + +variable "boot_diagnostics" { + type = bool + description = "(Optional) Enable or Disable boot diagnostics." + default = false +} + +variable "boot_diagnostics_sa_type" { + description = "(Optional) Storage account type for boot diagnostics." type = string - default = "azureuser" + default = "Standard_LRS" } variable "custom_data" { @@ -50,139 +65,232 @@ variable "custom_data" { default = "" } -variable "storage_account_type" { - description = "Defines the type of storage account to be created. Valid options are Standard_LRS, Standard_ZRS, Standard_GRS, Standard_RAGRS, Premium_LRS." - type = string - default = "Premium_LRS" +variable "data_disk_size_gb" { + description = "Storage data disk size size." + type = number + default = 30 } -variable "vm_size" { - description = "Specifies the size of the virtual machine." +variable "data_sa_type" { + description = "Data Disk Storage Account type." type = string - default = "Standard_D2s_v3" + default = "Standard_LRS" } -variable "nb_instances" { - description = "Specify the number of vm instances." - type = number - default = 1 +variable "delete_data_disks_on_termination" { + type = bool + description = "Delete data disks when machine is terminated." + default = false } -variable "vm_hostname" { - description = "local name of the Virtual Machine." - type = string - default = "myvm" +variable "delete_os_disk_on_termination" { + type = bool + description = "Delete OS disk when machine is terminated." + default = false } -variable "vm_os_simple" { - description = "Specify UbuntuServer, WindowsServer, RHEL, openSUSE-Leap, CentOS, Debian, CoreOS and SLES to get the latest image version of the specified os. Do not provide this value if a custom value is used for vm_os_publisher, vm_os_offer, and vm_os_sku." - type = string - default = "" +variable "enable_accelerated_networking" { + type = bool + description = "(Optional) Enable accelerated networking on Network interface." + default = false } -variable "vm_os_id" { - description = "The resource ID of the image that you want to deploy if you are using a custom image.Note, need to provide is_windows_image = true for windows custom images." +variable "enable_ssh_key" { + type = bool + description = "(Optional) Enable ssh key authentication in Linux virtual Machine." + default = true +} + +# Why use object as type? We use this variable in `count` expression, if we use a newly created `azurerm_storage_account.primary_blob_endpoint` as uri directly, then Terraform would complain that it cannot determine the value of `count` during the plan phase, so we wrap the `uri` with an object. +variable "external_boot_diagnostics_storage" { + description = "(Optional) The Storage Account's Blob Endpoint which should hold the virtual machine's diagnostic files. Set this argument would disable the creation of `azurerm_storage_account` resource." + type = object({ + uri = string + }) + default = null + validation { + condition = var.external_boot_diagnostics_storage == null ? true : var.external_boot_diagnostics_storage.uri != null + error_message = "`var.external_boot_diagnostics_storage.uri` cannot be `null`" + } +} + +variable "extra_disks" { + description = "(Optional) List of extra data disks attached to each virtual machine." + type = list(object({ + name = string + size = number + })) + default = [] +} + +variable "extra_ssh_keys" { + description = "Same as ssh_key, but allows for setting multiple public keys. Set your first key in ssh_key, and the extras here." + type = list(string) + default = [] +} + +variable "identity_ids" { + description = "Specifies a list of user managed identity ids to be assigned to the VM." + type = list(string) + default = [] +} + +variable "identity_type" { + description = "The Managed Service Identity Type of this Virtual Machine." type = string default = "" } +variable "is_marketplace_image" { + description = "Boolean flag to notify when the image comes from the marketplace." + type = bool + nullable = false + default = false +} + variable "is_windows_image" { description = "Boolean flag to notify when the custom image is windows based." type = bool default = false } -variable "vm_os_publisher" { - description = "The name of the publisher of the image that you want to deploy. This is ignored when vm_os_id or vm_os_simple are provided." +variable "license_type" { + description = "Specifies the BYOL Type for this Virtual Machine. This is only applicable to Windows Virtual Machines. Possible values are Windows_Client and Windows_Server" type = string - default = "" + default = null } -variable "vm_os_offer" { - description = "The name of the offer of the image that you want to deploy. This is ignored when vm_os_id or vm_os_simple are provided." +variable "location" { + description = "(Optional) The location in which the resources will be created." type = string - default = "" + default = null } -variable "vm_os_sku" { - description = "The sku of the image that you want to deploy. This is ignored when vm_os_id or vm_os_simple are provided." +variable "managed_data_disk_encryption_set_id" { + description = "(Optional) The disk encryption set ID for the managed data disk attached using the azurerm_virtual_machine_data_disk_attachment resource." type = string - default = "" + default = null } -variable "vm_os_version" { - description = "The version of the image that you want to deploy. This is ignored when vm_os_id or vm_os_simple are provided." +variable "name_template_availability_set" { + description = "The name template for the availability set. The following replacements are automatically made: `$${vm_hostname}` => `var.vm_hostname`. All other text can be set as desired." type = string - default = "latest" + default = "$${vm_hostname}-avset" } -variable "tags" { - type = map(string) - description = "A map of the tags to use on the resources that are deployed with this module." +variable "name_template_data_disk" { + description = "The name template for the data disks. The following replacements are automatically made: `$${vm_hostname}` => `var.vm_hostname`, `$${host_number}` => 'host index', `$${data_disk_number}` => 'data disk index'. All other text can be set as desired." + type = string + default = "$${vm_hostname}-datadisk-$${host_number}-$${data_disk_number}" +} - default = { - source = "terraform" - } +variable "name_template_extra_disk" { + description = "The name template for the extra disks. The following replacements are automatically made: `$${vm_hostname}` => `var.vm_hostname`, `$${host_number}` => 'host index', `$${extra_disk_name}` => 'name of extra disk'. All other text can be set as desired." + type = string + default = "$${vm_hostname}-extradisk-$${host_number}-$${extra_disk_name}" } -variable "allocation_method" { - description = "Defines how an IP address is assigned. Options are Static or Dynamic." +variable "name_template_network_interface" { + description = "The name template for the network interface. The following replacements are automatically made: `$${vm_hostname}` => `var.vm_hostname`, `$${host_number}` => 'host index'. All other text can be set as desired." type = string - default = "Dynamic" + default = "$${vm_hostname}-nic-$${host_number}" } -variable "nb_public_ip" { - description = "Number of public IPs to assign corresponding to one IP per vm. Set to 0 to not assign any public IP addresses." - type = number - default = 1 +variable "name_template_network_security_group" { + description = "The name template for the network security group. The following replacements are automatically made: `$${vm_hostname}` => `var.vm_hostname`. All other text can be set as desired." + type = string + default = "$${vm_hostname}-nsg" } -variable "delete_os_disk_on_termination" { - type = bool - description = "Delete datadisk when machine is terminated." - default = false +variable "name_template_public_ip" { + description = "The name template for the public ip. The following replacements are automatically made: `$${vm_hostname}` => `var.vm_hostname`, `$${ip_number}` => 'public ip index'. All other text can be set as desired." + type = string + default = "$${vm_hostname}-pip-$${ip_number}" } -variable "data_sa_type" { - description = "Data Disk Storage Account type." +variable "name_template_vm_linux" { + description = "The name template for the Linux virtual machine. The following replacements are automatically made: `$${vm_hostname}` => `var.vm_hostname`, `$${host_number}` => 'host index'. All other text can be set as desired." type = string - default = "Standard_LRS" + default = "$${vm_hostname}-vmLinux-$${host_number}" } -variable "data_disk_size_gb" { - description = "Storage data disk size size." - type = number - default = 30 +variable "name_template_vm_linux_os_disk" { + description = "The name template for the Linux VM OS disk. The following replacements are automatically made: `$${vm_hostname}` => `var.vm_hostname`, `$${host_number}` => 'host index'. All other text can be set as desired." + type = string + default = "osdisk-$${vm_hostname}-$${host_number}" } -variable "boot_diagnostics" { - type = bool - description = "(Optional) Enable or Disable boot diagnostics." - default = false +variable "name_template_vm_windows" { + description = "The name template for the Windows virtual machine. The following replacements are automatically made: `$${vm_hostname}` => `var.vm_hostname`, `$${host_number}` => 'host index'. All other text can be set as desired." + type = string + default = "$${vm_hostname}-vmWindows-$${host_number}" } -variable "boot_diagnostics_sa_type" { - description = "(Optional) Storage account type for boot diagnostics." +variable "name_template_vm_windows_os_disk" { + description = "The name template for the Windows VM OS disk. The following replacements are automatically made: `$${vm_hostname}` => `var.vm_hostname`, `$${host_number}` => 'host index'. All other text can be set as desired." type = string - default = "Standard_LRS" + default = "$${vm_hostname}-osdisk-$${host_number}" } -variable "enable_accelerated_networking" { - type = bool - description = "(Optional) Enable accelerated networking on Network interface." - default = false +variable "nb_data_disk" { + description = "(Optional) Number of the data disks attached to each virtual machine." + type = number + default = 0 } -variable "enable_ssh_key" { +variable "nb_instances" { + description = "Specify the number of vm instances." + type = number + default = 1 +} + +variable "nb_public_ip" { + description = "Number of public IPs to assign corresponding to one IP per vm. Set to 0 to not assign any public IP addresses." + type = number + default = 1 +} + +variable "nested_data_disks" { + description = "(Optional) When `true`, use nested data disks directly attached to the VM. When `false`, use azurerm_virtual_machine_data_disk_attachment resource to attach the data disks after the VM is created. Default is `true`." type = bool - description = "(Optional) Enable ssh key authentication in Linux virtual Machine." + nullable = false default = true } -variable "nb_data_disk" { - description = "(Optional) Number of the data disks attached to each virtual machine." - type = number - default = 0 +variable "network_security_group" { + description = "The network security group we'd like to bind with virtual machine. Set this variable will disable the creation of `azurerm_network_security_group` and `azurerm_network_security_rule` resources." + type = object({ + id = string + }) + default = null + validation { + condition = var.network_security_group == null ? true : var.network_security_group.id != null + error_message = "When `var.network_security_group` is not `null`, `var.network_security_group.id` is required." + } +} + +variable "os_profile_secrets" { + description = "Specifies a list of certificates to be installed on the VM, each list item is a map with the keys source_vault_id, certificate_url and certificate_store." + type = list(map(string)) + default = [] +} + +variable "public_ip_dns" { + description = "Optional globally unique per datacenter region domain name label to apply to each public ip address. e.g. thisvar.varlocation.cloudapp.azure.com where you specify only thisvar here. This is an array of names which will pair up sequentially to the number of public ips defined in var.nb_public_ip. One name or empty string is required for every public ip. If no public ip is desired, then set this to an array with a single empty string." + type = list(string) + default = [null] +} + +variable "public_ip_sku" { + description = "Defines the SKU of the Public IP. Accepted values are Basic and Standard. Defaults to Basic." + type = string + default = "Basic" +} + +variable "remote_port" { + description = "Remote tcp port to be used for access to the vms created via the nsg applied to the nics." + type = string + default = "" } variable "source_address_prefixes" { @@ -191,20 +299,154 @@ variable "source_address_prefixes" { default = ["0.0.0.0/0"] } -variable "license_type" { - description = "Specifies the BYOL Type for this Virtual Machine. This is only applicable to Windows Virtual Machines. Possible values are Windows_Client and Windows_Server" +variable "ssh_key" { + description = "Path to the public key to be used for ssh access to the VM. Only used with non-Windows vms and can be left as-is even if using Windows vms. If specifying a path to a certification on a Windows machine to provision a linux vm use the / in the path versus backslash.e.g. c : /home/id_rsa.pub." + type = string + default = "~/.ssh/id_rsa.pub" +} + +variable "ssh_key_values" { + description = "List of Public SSH Keys values to be used for ssh access to the VMs." + type = list(string) + default = [] +} + +variable "storage_account_type" { + description = "Defines the type of storage account to be created. Valid options are Standard_LRS, Standard_ZRS, Standard_GRS, Standard_RAGRS, Premium_LRS." type = string + default = "Premium_LRS" +} + +variable "storage_os_disk_size_gb" { + description = "(Optional) Specifies the size of the data disk in gigabytes." + type = number default = null } -variable "identity_type" { - description = "The Managed Service Identity Type of this Virtual Machine." +variable "tags" { + type = map(string) + description = "A map of the tags to use on the resources that are deployed with this module." + + default = { + source = "terraform" + } +} + +variable "vm_extension" { + description = "(Deprecated) This variable has been superseded by the `vm_extensions`. Argument to create `azurerm_virtual_machine_extension` resource, the argument descriptions could be found at [the document](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine_extension)." + type = object({ + name = string + publisher = string + type = string + type_handler_version = string + auto_upgrade_minor_version = optional(bool) + automatic_upgrade_enabled = optional(bool) + failure_suppression_enabled = optional(bool, false) + settings = optional(string) + protected_settings = optional(string) + protected_settings_from_key_vault = optional(object({ + secret_url = string + source_vault_id = string + })) + }) + default = null + sensitive = true # Because `protected_settings` is sensitive +} + +variable "vm_extensions" { + description = "Argument to create `azurerm_virtual_machine_extension` resource, the argument descriptions could be found at [the document](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine_extension)." + type = set(object({ + name = string + publisher = string + type = string + type_handler_version = string + auto_upgrade_minor_version = optional(bool) + automatic_upgrade_enabled = optional(bool) + failure_suppression_enabled = optional(bool, false) + settings = optional(string) + protected_settings = optional(string) + protected_settings_from_key_vault = optional(object({ + secret_url = string + source_vault_id = string + })) + })) + # tflint-ignore: terraform_sensitive_variable_no_default + default = [] + nullable = false + sensitive = true # Because `protected_settings` is sensitive + validation { + condition = length(var.vm_extensions) == length(distinct([ + for e in var.vm_extensions : e.type + ])) + error_message = "`type` in `vm_extensions` must be unique." + } + validation { + condition = length(var.vm_extensions) == length(distinct([ + for e in var.vm_extensions : e.name + ])) + error_message = "`name` in `vm_extensions` must be unique." + } +} + +variable "vm_hostname" { + description = "local name of the Virtual Machine." + type = string + default = "myvm" +} + +variable "vm_os_id" { + description = "The resource ID of the image that you want to deploy if you are using a custom image.Note, need to provide is_windows_image = true for windows custom images." type = string default = "" } -variable "identity_ids" { - description = "Specifies a list of user managed identity ids to be assigned to the VM." - type = list(string) - default = [] +variable "vm_os_offer" { + description = "The name of the offer of the image that you want to deploy. This is ignored when vm_os_id or vm_os_simple are provided." + type = string + default = "" +} + +variable "vm_os_publisher" { + description = "The name of the publisher of the image that you want to deploy. This is ignored when vm_os_id or vm_os_simple are provided." + type = string + default = "" +} + +variable "vm_os_simple" { + description = "Specify UbuntuServer, WindowsServer, RHEL, openSUSE-Leap, CentOS, Debian, CoreOS and SLES to get the latest image version of the specified os. Do not provide this value if a custom value is used for vm_os_publisher, vm_os_offer, and vm_os_sku." + type = string + default = "" +} + +variable "vm_os_sku" { + description = "The sku of the image that you want to deploy. This is ignored when vm_os_id or vm_os_simple are provided." + type = string + default = "" +} + +variable "vm_os_version" { + description = "The version of the image that you want to deploy. This is ignored when vm_os_id or vm_os_simple are provided." + type = string + default = "latest" +} + +variable "vm_size" { + description = "Specifies the size of the virtual machine." + type = string + default = "Standard_D2s_v3" +} + +# Why we use `zone` not `zones` as `azurerm_virtual_machine.zones`? +# `azurerm_virtual_machine.zones` is [a list of single Az](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine#zones), the maximum length is `1` +# so we can only pass one zone per vm instance. +# Why don't we use [`element`](https://developer.hashicorp.com/terraform/language/functions/element) function? +# The `element` function act as mod operator, it will iterate the vm instances, meanwhile +# we must keep the vm and public ip in the same zone. +# The vm's count is controlled by `var.nb_instances` and public ips' count is controled by `var.nb_public_ip`, +# it would be hard for us to keep the vm and public ip in the same zone once `var.nb_instances` doesn't equal to `var.nb_public_ip` +# So, we decide that one module instance supports one zone only to avoid this dilemma. +variable "zone" { + description = "(Optional) The Availability Zone which the Virtual Machine should be allocated in, only one zone would be accepted. If set then this module won't create `azurerm_availability_set` resource. Changing this forces a new resource to be created." + type = string + default = null } diff --git a/versions.tf b/versions.tf new file mode 100644 index 0000000..3fd8543 --- /dev/null +++ b/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 1.3" + + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = ">= 3.11, < 4.0" + } + random = { + source = "hashicorp/random" + version = ">=3.0.0" + } + } +} \ No newline at end of file