diff --git a/.azure/backendTemplate.tf b/.azure/backendTemplate.tf new file mode 100644 index 0000000..4a06ca3 --- /dev/null +++ b/.azure/backendTemplate.tf @@ -0,0 +1,10 @@ +terraform { + backend "azurerm" { + resource_group_name = "resourcegroup" + storage_account_name = "tfdatas" + container_name = "tfstate" + key = "{{.GroupName}}.tfstate" + use_azuread_auth = true + subscription_id = "26ad903f-2330-429d-8389-864ac35c4350" + } +} diff --git a/.azure/hooks/export b/.azure/hooks/export new file mode 100644 index 0000000..aec92bc --- /dev/null +++ b/.azure/hooks/export @@ -0,0 +1,7 @@ +#!/bin/bash +group_paths=$(python -c "import json; print('\n'.join([x['groupPath'] for x in json.load(open('./.azure/export.json'))]))") + +for path in $group_paths +do + mkdir -p $path +done \ No newline at end of file diff --git a/.azure/hooks/generate b/.azure/hooks/generate new file mode 100644 index 0000000..a181a72 --- /dev/null +++ b/.azure/hooks/generate @@ -0,0 +1,88 @@ +#!/bin/bash +set -e + +echo "Generating deployment pipeline" + +sed -i $'s/\r$//' ./.stages +readarray -t stages < ./.stages + +groupTemplate=' + {{.GroupName}}: + uses: ./.github/workflows/site-cd-workflow.yml + with: + working-directory: {{.Stage}}/{{.GroupName}} + secrets: inherit + needs: [{{.Stage}}] +' + +stageTemplate=' + {{.Stage}}: + name: {{.Stage}} + needs: [{{.GroupList}}] + runs-on: ubuntu-latest + steps: + - run: echo "running {{.Stage}} stage" +' + +workflow='name: Terraform apply infra change + +on: + push: + branches: ["main"] + workflow_dispatch: + +permissions: + id-token: write + contents: read + +jobs:' +backendTemplate=$(<.azure/backendTemplate.tf) + +for count in "${!stages[@]}"; do + stage=${stages[$count]} + + if [ $count -eq 0 ]; then + stagejob=$(echo "$stageTemplate" | sed "s/{{.Stage}}/$stage/g" | grep -v 'needs: \[.*\]') + workflow+="$stagejob" + else + groupList=${stages[$count-1]} + pushd ./${stages[$count-1]} > /dev/null + for d in */ ; do + if [[ $d == "*/" ]]; then + break + fi + group=$(echo "$d" | sed 's/\///g' | sed 's/ /_/g') + groupList="$groupList,$group" + done + stagejob=$(echo "$stageTemplate" | sed "s/{{.Stage}}/$stage/g" | sed "s/{{.GroupList}}/$groupList/g") + workflow+="$stagejob" + popd > /dev/null + fi + + pushd ./$stage > /dev/null + for d in */ ; do + if [[ $d == "*/" ]]; then + break + fi + group=$(echo "$d" | sed 's/\///g' | sed 's/ /_/g') + groupjob=$(echo "$groupTemplate" | sed "s/{{.GroupName}}/$group/g" | sed "s/{{.Stage}}/$stage/g") + workflow+="$groupjob" + + #generate backend config file + backendConfigFile="./${group}/backend.tf" + echo $backendConfigFile + echo "$backendTemplate" | sed "s/{{.GroupName}}/$group/g" > "$backendConfigFile" + git add $backendConfigFile + done + popd > /dev/null +done + +# create a workflow file +workflowfile="./.github/workflows/deploy-infra.yml" +if [ -f "$workflowfile" ]; then + rm "$workflowfile" +fi +echo "$workflow" > "$workflowfile" +git add $workflowfile + +echo "Generated deployment pipeline" diff --git a/.azure/hooks/pre-commit b/.azure/hooks/pre-commit new file mode 100644 index 0000000..8fff2cd --- /dev/null +++ b/.azure/hooks/pre-commit @@ -0,0 +1,12 @@ +#!/bin/bash +set -e + +if [ -f ./.azure/scale.csv ]; then + ./.azure/hooks/scale +fi + +if [ -f ./.azure/export.json ]; then + ./.azure/hooks/export +fi + +./.azure/hooks/generate diff --git a/.azure/hooks/scale b/.azure/hooks/scale new file mode 100644 index 0000000..5751e0b --- /dev/null +++ b/.azure/hooks/scale @@ -0,0 +1,23 @@ +#/bin/bash +set -e + +gawk -v RS='"' 'NR % 2 == 0 { gsub(/\n/, "") } { printf("%s%s", $0, RT) }' ./.azure/scale.csv > ./.azure/scale.csv.tmp +echo "" >> ./.azure/scale.csv.tmp + +skip_headers=2 + +while IFS=, read -r stage siteId others +do + if ((skip_headers)) + then + ((skip_headers--)) + else + siteId=$(echo $siteId | tr -d '"') + echo "Stage:$stage, SiteId: $siteId" + # create folder if site id is not empty + if [ ! -z "$siteId" ] + then + mkdir -p ./$stage/$siteId + fi + fi +done < ./.azure/scale.csv.tmp diff --git a/.azure/scale.csv b/.azure/scale.csv new file mode 100644 index 0000000..32d88fc --- /dev/null +++ b/.azure/scale.csv @@ -0,0 +1,7 @@ +stage,siteId,location,domainFqdn +dev,testinstance," ""eastus"""," ""jumpstart.local""" +dev,, +dev,, +dev,, +dev,, +dev,, \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..a7dbc30 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +* text=auto + +*.exe binary +*.png binary +*.jpg binary +*.jpeg binary +*.pdf binary diff --git a/.github/workflows/deploy-infra.yml b/.github/workflows/deploy-infra.yml new file mode 100644 index 0000000..36f4ba8 --- /dev/null +++ b/.github/workflows/deploy-infra.yml @@ -0,0 +1,35 @@ +name: Terraform apply infra change + +on: + push: + branches: ["main"] + workflow_dispatch: + +permissions: + id-token: write + contents: read + +jobs: + dev: + name: dev + needs: [] + uses: ./.github/workflows/list-and-run.yml + secrets: inherit + with: + directory: dev + + qa: + name: qa + needs: [dev] + uses: ./.github/workflows/list-and-run.yml + secrets: inherit + with: + directory: qa + + prod: + name: prod + needs: [dev,qa] + uses: ./.github/workflows/list-and-run.yml + secrets: inherit + with: + directory: prod diff --git a/.github/workflows/export.yml b/.github/workflows/export.yml new file mode 100644 index 0000000..6c1fc7a --- /dev/null +++ b/.github/workflows/export.yml @@ -0,0 +1,112 @@ +name: Export Azure resource into config + +on: + push: + branches: + - '**' + - '!main' + paths: + - '.azure/export.json' + workflow_call: + inputs: + branch: + required: true + type: string + +permissions: + contents: write + id-token: write + +env: + ARM_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + ARM_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + ARM_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + ARM_USE_OIDC: true + TF_VAR_subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + TF_VAR_hci_0_local_admin_user: ${{ secrets.localAdminUser }} + TF_VAR_hci_0_local_admin_password: ${{ secrets.localAdminPassword }} + TF_VAR_domain_admin_user: ${{ secrets.domainAdminUser }} + TF_VAR_domain_admin_password: ${{ secrets.domainAdminPassword }} + TF_VAR_hci_0_deployment_user_password: ${{ secrets.deploymentUserPassword }} + TF_VAR_hci_0_service_principal_id: ${{ secrets.servicePrincipalId }} + TF_VAR_hci_0_service_principal_secret: ${{ secrets.servicePrincipalSecret }} + TF_VAR_rp_service_principal_object_id: ${{ secrets.rpServicePrincipalObjectId }} + TF_VAR_vm_admin_password: ${{ secrets.vmAdminPassword }} + TF_VAR_domain_join_password: ${{ secrets.domainJoinPassword }} + HCI_RP_SP_ID: ${{ secrets.rpServicePrincipalObjectId }} + +jobs: + export: + environment: terraform + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + # checkout to input branch when input branch is not empty + - name: Checkout to input branch + if: ${{ inputs.branch != '' }} + run: | + git fetch origin ${{ inputs.branch }} + git checkout ${{ inputs.branch }} + # Install node + - uses: actions/setup-node@v4 + with: + node-version: latest + - run: node --version + # Install the latest version of Terraform CLI + - name: Setup Terraform + uses: hashicorp/setup-terraform@v2 + with: + terraform_wrapper: false + # check terraform version + - name: Check terraform version + run: terraform version + # az login + - name: Log in to Azure using OIDC + uses: azure/login@v1 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + # check first 18 characters of az account user name + - name: Check az account + run: az account show --query user.name --output tsv | cut -c 1-18 + # Download az-edge-module-export + - name: Download az-edge-module-export + run: | + wget "https://aka.ms/az-edge-module-export-linux-amd64" -O az-edge-module-export + chmod +x az-edge-module-export + ./az-edge-module-export -v + # Download az-edge-site-scale + - name: Download az-edge-site-scale + run: | + wget "https://aka.ms/az-edge-site-scale-linux-amd64" -O az-edge-site-scale + chmod +x az-edge-site-scale + ./az-edge-site-scale -v + # Run az-edge-module-export + - name: Run az-edge-module-export + run: | + ./az-edge-module-export -c ./.azure/export.json -b ./.azure/backendTemplate.tf + rm ./az-edge-module-export + # Generate sample csv file to scale + - name: Run az-edge-site-scale generate + run: | + mkdir -p ./.azure/scale + cat ./.azure/export.json | jq -r '.[]|[.baseModulePath, .groupPath] | @tsv' | + while IFS=$'\t' read -r baseModulePath groupPath; do + name=$(echo $baseModulePath | rev | cut -d '/' -f 1 | rev) + ./az-edge-site-scale generate -c ./.azure/scale/$name.csv -s $groupPath + done + rm ./az-edge-site-scale + - name: Clean up + run: | + rm ./.azure/export.json + # Commit and push the changes + - name: Commit and push the changes + if: always() + run: | + git config --global user.email "exporter@iac.microsoft.com" + git config --global user.name "IaC Exporter" + git add . + git commit -m "Export Azure resource into config" + git push diff --git a/.github/workflows/list-and-run.yml b/.github/workflows/list-and-run.yml new file mode 100644 index 0000000..0588d77 --- /dev/null +++ b/.github/workflows/list-and-run.yml @@ -0,0 +1,51 @@ +name: List and Run + +on: + workflow_call: + inputs: + directory: + required: true + type: string + +jobs: + list: + runs-on: windows-latest + outputs: + matrix: ${{ steps.setTargets.outputs.matrix }} + apply: ${{ steps.setTargets.outputs.apply }} + steps: + # Checkout the repository to the GitHub Actions runner + - name: Checkout + uses: actions/checkout@v3 + + - name: List directory + id: setTargets + shell: pwsh + run: | + $inputDirectory = "${{ inputs.directory }}" + $fullPath = Join-Path $pwd ${{ inputs.directory }} + $sites = Get-ChildItem -Directory $fullPath + + $array = @() + foreach ($site in $sites) { + $array += @{ + 'siteId' = $site.Name + 'workingDirectory' = ($inputDirectory + '/' + $site.Name).Replace('\', '/') + } + } + $json = ConvertTo-Json -InputObject $array -Compress + + echo "matrix=$json" >> $env:GITHUB_OUTPUT + + $apply = if ($sites.Length -gt 0) { 'true' } else { 'false' } + echo "apply=$apply" >> $env:GITHUB_OUTPUT + apply: + needs: [list] + if: ${{ needs.list.outputs.apply == 'true' }} + strategy: + matrix: + site: ${{ fromJson(needs.list.outputs.matrix) }} + uses: ./.github/workflows/site-cd-workflow.yml + with: + working-directory: ${{ matrix.site.workingDirectory }} + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/scale.yml b/.github/workflows/scale.yml new file mode 100644 index 0000000..f177074 --- /dev/null +++ b/.github/workflows/scale.yml @@ -0,0 +1,73 @@ +name: Scale Edge Sites + +on: + push: + branches: + - '**' + - '!main' + paths: + - '.azure/scale.csv' + workflow_call: + inputs: + branch: + required: true + type: string + +permissions: + contents: write + id-token: write + pull-requests: write + issues: write + +jobs: + scale: + environment: terraform + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + # checkout to input branch when input branch is not empty + - name: Checkout to input branch + if: ${{ inputs.branch != '' }} + run: | + git fetch origin ${{ inputs.branch }} + git checkout ${{ inputs.branch }} + # Install node + - uses: actions/setup-node@v4 + with: + node-version: latest + - run: node --version + # az login + - name: Log in to Azure using OIDC + uses: azure/login@v1 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + # check first 18 characters of az account user name + - name: Check az account + run: az account show --query user.name --output tsv | cut -c 1-18 + # Download az-edge-site-scale + - name: Download az-edge-site-scale + run: | + wget "https://aka.ms/az-edge-site-scale-linux-amd64" -O az-edge-site-scale + chmod +x az-edge-site-scale + ./az-edge-site-scale -v + # Run az-edge-site-scale scale + - name: Run az-edge-site-scale scale + run: | + ./az-edge-site-scale scale -c ./.azure/scale.csv + rm ./az-edge-site-scale + - name: Clean up + run: | + rm ./.azure/scale.csv + # Commit and push the changes + - name: Commit and push the changes + if: always() + run: | + git config --global user.email "scaler@iac.microsoft.com" + git config --global user.name "IaC Scaler" + git add . + git commit -m "Scale more sites according to .azure/scale.csv" + git push + \ No newline at end of file diff --git a/.github/workflows/site-cd-workflow.yml b/.github/workflows/site-cd-workflow.yml new file mode 100644 index 0000000..34a6b54 --- /dev/null +++ b/.github/workflows/site-cd-workflow.yml @@ -0,0 +1,122 @@ +name: Site Deployment + +on: + workflow_call: + inputs: + working-directory: + required: true + type: string + +permissions: + id-token: write + contents: read + +env: + ARM_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + ARM_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + ARM_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + ARM_USE_OIDC: true + TF_VAR_subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + TF_VAR_local_admin_user: ${{ secrets.localAdminUser }} + TF_VAR_hci_0_local_admin_user: ${{ secrets.localAdminUser }} + TF_VAR_local_admin_password: ${{ secrets.localAdminPassword }} + TF_VAR_hci_0_local_admin_password: ${{ secrets.localAdminPassword }} + TF_VAR_domain_admin_user: ${{ secrets.domainAdminUser }} + TF_VAR_domain_admin_password: ${{ secrets.domainAdminPassword }} + TF_VAR_deployment_user_password: ${{ secrets.deploymentUserPassword }} + TF_VAR_hci_0_deployment_user_password: ${{ secrets.deploymentUserPassword }} + TF_VAR_service_principal_id: ${{ secrets.servicePrincipalId }} + TF_VAR_hci_0_service_principal_id: ${{ secrets.servicePrincipalId }} + TF_VAR_service_principal_secret: ${{ secrets.servicePrincipalSecret }} + TF_VAR_hci_0_service_principal_secret: ${{ secrets.servicePrincipalSecret }} + TF_VAR_rp_service_principal_object_id: ${{ secrets.rpServicePrincipalObjectId }} + TF_VAR_vm_admin_password: ${{ secrets.vmAdminPassword }} + TF_VAR_domain_join_password: ${{ secrets.domainJoinPassword }} + +jobs: + terraform: + name: ${{ inputs.working-directory }} + # runs-on: [windows-latest] + runs-on: [self-hosted] + environment: terraform + + # Use the Bash shell regardless whether the GitHub Actions runner is ubuntu-latest, macos-latest, or windows-latest + defaults: + run: + shell: bash + working-directory: ${{ inputs.working-directory }} + + steps: + # Checkout the repository to the GitHub Actions runner + - name: Checkout + uses: actions/checkout@v3 + + - name: Log in to Azure using OIDC + uses: azure/login@v1 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + + # Install node + - uses: actions/setup-node@v4 + with: + node-version: latest + - run: node --version + + # Install the latest version of Terraform CLI + - name: Setup Terraform + uses: hashicorp/setup-terraform@v2 + + # Initialize a new or existing Terraform working directory by creating initial files, loading any remote state, downloading modules, etc. + - name: Terraform Init + run: terraform init + + - run: mkdir -p "${{ runner.temp }}/${{ inputs.working-directory }}" + + # Generates an execution plan for Terraform + - name: Terraform Plan + run: terraform plan -input=false -out="${{ runner.temp }}/${{ inputs.working-directory }}/out.tfplan" + + - name: Upload the plan + uses: actions/upload-artifact@v3 + with: + name: tf-plan + path: "${{ runner.temp }}/${{ inputs.working-directory }}/out.tfplan" + + # Telemetry: Plan Successful + - name: "Telemetry: Plan Successful" + if: success() + uses: Azure/IaC-Telemetry@main + with: + event-name: "plan-success" + directory: ${{ inputs.working-directory }} + + # Telemetry: Plan Failed + - name: "Telemetry: Plan Failed" + if: failure() + uses: Azure/IaC-Telemetry@main + with: + event-name: "plan-failure" + directory: ${{ inputs.working-directory }} + + # On push to $default-branch, build or change infrastructure according to Terraform configuration files + # Note: It is recommended to set up a required "strict" status check in your repository for "Terraform Cloud". See the documentation on "strict" required status checks for more information: https://help.github.com/en/github/administering-a-repository/types-of-required-status-checks + - name: Terraform Apply + run: terraform apply -auto-approve -input=false "${{ runner.temp }}/${{ inputs.working-directory }}/out.tfplan" + + # Telemetry: Apply Successful + - name: "Telemetry: Apply Successful" + if: success() + uses: Azure/IaC-Telemetry@main + with: + event-name: "apply-success" + directory: ${{ inputs.working-directory }} + + # Telemetry: Apply Failed + - name: "Telemetry: Apply Failed" + if: failure() + uses: Azure/IaC-Telemetry@main + with: + event-name: "apply-failure" + directory: ${{ inputs.working-directory }} \ No newline at end of file diff --git a/.github/workflows/terraform-plan.yml b/.github/workflows/terraform-plan.yml new file mode 100644 index 0000000..3cd6023 --- /dev/null +++ b/.github/workflows/terraform-plan.yml @@ -0,0 +1,230 @@ +name: Terraform plan check + +on: + pull_request: + branches: ["main"] + +permissions: + id-token: write + contents: read + pull-requests: write + issues: write + +env: + ARM_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + ARM_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + ARM_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + ARM_USE_OIDC: true + TF_VAR_subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + TF_VAR_local_admin_user: ${{ secrets.localAdminUser }} + TF_VAR_hci_0_local_admin_user: ${{ secrets.localAdminUser }} + TF_VAR_local_admin_password: ${{ secrets.localAdminPassword }} + TF_VAR_hci_0_local_admin_password: ${{ secrets.localAdminPassword }} + TF_VAR_domain_admin_user: ${{ secrets.domainAdminUser }} + TF_VAR_domain_admin_password: ${{ secrets.domainAdminPassword }} + TF_VAR_deployment_user_password: ${{ secrets.deploymentUserPassword }} + TF_VAR_hci_0_deployment_user_password: ${{ secrets.deploymentUserPassword }} + TF_VAR_service_principal_id: ${{ secrets.servicePrincipalId }} + TF_VAR_hci_0_service_principal_id: ${{ secrets.servicePrincipalId }} + TF_VAR_service_principal_secret: ${{ secrets.servicePrincipalSecret }} + TF_VAR_hci_0_service_principal_secret: ${{ secrets.servicePrincipalSecret }} + TF_VAR_rp_service_principal_object_id: ${{ secrets.rpServicePrincipalObjectId }} + TF_VAR_vm_admin_password: ${{ secrets.vmAdminPassword }} + TF_VAR_domain_join_password: ${{ secrets.domainJoinPassword }} + +jobs: + provide_paths: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Check diff to see what content is changed + id: check_diff + run: | + ## the || true is to avoid the exit code 1 of grep when no tf files are changed + changedFiles=$(git diff --name-only origin/main HEAD -- | grep ".tf" || true) + if [ -z "$changedFiles" ]; then + echo "No terraform files are changed" + echo "isModuleChanged=false" >> $GITHUB_OUTPUT + echo "planNeeded=false" >> $GITHUB_OUTPUT + exit 0 + fi + echo "changedFiles=$changedFiles" + if [[ $changedFiles == *"modules"* ]]; then + echo "changedFiles contain modules" + echo "isModuleChanged=true" >> $GITHUB_OUTPUT + else + echo "changedFiles do not contain modules" + echo "isModuleChanged=false" >> $GITHUB_OUTPUT + fi + ## get changed directories + echo "generate directory based on changed files" + directories=$(echo "$changedFiles" | xargs -n 1 dirname | sort -u | tr '\n' ' ') + echo "directory=$directories" >> $GITHUB_OUTPUT + + - name: Set matrix + id: set-matrix + run: | + array=() + if !${{ steps.check_diff.outputs.planNeeded }}; then + echo "No directories to process, no need to plan" + echo "planNeeded=false" >> $GITHUB_OUTPUT + exit 0 + fi + if ${{ steps.check_diff.outputs.isModuleChanged }}; then + echo "the module is changed, so generate the matrix based on the stages and sites" + readarray -t stages < ./.stages + for count in "${!stages[@]}"; do + stage=${stages[$count]} + pushd ./$stage > /dev/null + for d in */ ; do + if [[ $d == "*/" ]]; then + break + fi + group=$(echo "$d" | sed 's/\///g' | sed 's/ /_/g') + array+=("$stage/$group") + break + done + popd > /dev/null + done + fi + + echo "also generate the matrix based on the changed directories" + IFS=' ' read -ra dirs <<< "${{ steps.check_diff.outputs.directory }}" + for i in "${dirs[@]}"; do + if [[ "$i" != ".azure" && "$i" != *"module"* ]] + then + array+=("$i") + fi + done + + if [ ${#array[@]} -eq 0 ]; then + echo "No directories to process" + echo "planNeeded=false" >> $GITHUB_OUTPUT + else + echo "planNeeded=true" >> $GITHUB_OUTPUT + fi + echo "remove duplicates directory and sort" + sorted_array=($(echo "${array[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' ')) + echo "check directories is exist" + checkarray=() + for i in "${sorted_array[@]}"; do + if [ -d "$i" ]; then + checkarray+=("$i") + fi + done + json=$(jq --compact-output --null-input '$ARGS.positional' --args -- "${checkarray[@]}") + echo "matrix=$json" + echo "matrix=$json" >> $GITHUB_OUTPUT + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + planNeeded: ${{ steps.check_diff.outputs.planNeeded }} + terraform_plan_comments: + runs-on: ubuntu-latest + needs: provide_paths + if: needs.provide_paths.outputs.planNeeded != 'false' + strategy: + matrix: + path: ${{fromJson(needs.provide_paths.outputs.matrix)}} + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Log in to Azure using OIDC + uses: azure/login@v1 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + + # Install node + - uses: actions/setup-node@v4 + with: + node-version: latest + - run: node --version + + # Install the latest version of Terraform CLI + - name: Setup Terraform + uses: hashicorp/setup-terraform@v2 + with: + terraform_wrapper: false + + # Initialize a new or existing Terraform working directory by creating initial files, loading any remote state, downloading modules, etc. + - name: Terraform Init + id: init + run: terraform init + working-directory: ${{ matrix.path }} + + - name: Terraform Fmt + id: fmt + run: terraform fmt -check + working-directory: ${{ matrix.path }} + continue-on-error: true + + - name: Terraform Validate + id: validate + run: terraform validate -no-color + working-directory: ${{ matrix.path }} + + - run: mkdir -p "${{ runner.temp }}/${{ matrix.path }}" + + # Generates an execution plan for Terraform + - name: Terraform Plan + id: plan + run: terraform plan -input=false -lock=false -out "${{ runner.temp }}/${{ matrix.path }}/terraform.plan" + working-directory: ${{ matrix.path }} + + - run: terraform show -no-color "${{ runner.temp }}/${{ matrix.path }}/terraform.plan" > "${{ runner.temp }}/${{ matrix.path }}/terraform.text" + working-directory: ${{ matrix.path }} + + # generate json output + - run: | + # this is a known issue for ahmadnassri/action-terraform-report when plan result is no change + terraform show -json ${{ runner.temp }}/${{ matrix.path }}/terraform.plan > ${{ runner.temp }}/${{ matrix.path }}/tf-temp.json + # check if the .resource_changes is null in the tf.plan file and if it is, + # add an empty array to the json file else don't do anything. + if [ "$(jq '.resource_changes' ${{ runner.temp }}/${{ matrix.path }}/tf-temp.json)" == "null" ]; then + echo "resource_changes is null" + jq --argjson to_add '{"resource_changes":[]}' '. * $to_add' ${{ runner.temp }}/${{ matrix.path }}/tf-temp.json > ${{ runner.temp }}/${{ matrix.path }}/terraform.json + else + echo "resource_changes is not null" + cp ${{ runner.temp }}/${{ matrix.path }}/tf-temp.json ${{ runner.temp }}/${{ matrix.path }}/terraform.json + fi + working-directory: ${{ matrix.path }} + if: steps.plan.outcome == 'success' + + - name: Update Pull Request + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const output = `#### Terraform plan run for \`${{ matrix.path }}\` + #### Terraform Initialization 🤖\`${{ steps.init.outcome }}\` + #### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\` + #### You can run terraform fmt to fix the formatting issues + #### Terraform Validation 🤖\`${{ steps.validate.outcome }}\` + #### Terraform Plan 🤖\`${{ steps.plan.outcome }}\` + + *Pushed by: @${{ github.actor }}, Action: \`${{ github.event_name }}\`*`; + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: output + }) + if: always() + + - uses: ahmadnassri/action-terraform-report@v3 + with: + # tell the action the plan outputs + terraform-text: ${{ runner.temp }}/${{ matrix.path }}/terraform.text + terraform-json: ${{ runner.temp }}/${{ matrix.path }}/terraform.json + remove-stale-reports: false + if: steps.plan.outcome == 'success' + + - name: Terraform Plan Status + if: steps.plan.outcome == 'failure' + run: exit 1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cb3a406 --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +# Compiled files +*.tfstate +*.tfstate.backup + +# Terraform provider plugins +.terraform/ +.terraform +.terraform.lock.hcl + +# IDE and editor files +.vscode/ +.idea/ +*.swp +*.swo +*.swn +*.bak + +# Temporary files +*.tmp +*.log +*.gz +*.zip +*.tar +*.tgz + +# OS-specific files +.DS_Store +Thumbs.db + +**/temp/* +*.tmp +az-edge-module-export +az-edge-module-export.exe +az-edge-site-scale +az-edge-site-scale.exe diff --git a/.stages b/.stages new file mode 100644 index 0000000..599bd90 --- /dev/null +++ b/.stages @@ -0,0 +1,3 @@ +dev +qa +prod diff --git a/BUILD.bazel b/BUILD.bazel new file mode 100644 index 0000000..6d5fb71 --- /dev/null +++ b/BUILD.bazel @@ -0,0 +1,10 @@ +filegroup( + name = "profilefiles-tar-files", + srcs = glob( + [ + "**", + ], + exclude = ["BUILD.bazel"], + ), + visibility = ["//visibility:public"], +) diff --git a/dev/.gitignore b/dev/.gitignore new file mode 100644 index 0000000..6423733 --- /dev/null +++ b/dev/.gitignore @@ -0,0 +1,4 @@ + +*.tfstate +*.tfstate.backup +*.tfvars \ No newline at end of file diff --git a/dev/testinstance/.gitignore b/dev/testinstance/.gitignore new file mode 100644 index 0000000..34a308b --- /dev/null +++ b/dev/testinstance/.gitignore @@ -0,0 +1 @@ +*.tfvars \ No newline at end of file diff --git a/dev/testinstance/backend.tf b/dev/testinstance/backend.tf new file mode 100644 index 0000000..2cca429 --- /dev/null +++ b/dev/testinstance/backend.tf @@ -0,0 +1,10 @@ +terraform { + backend "azurerm" { + resource_group_name = "resourcegroup" + storage_account_name = "tfdatas" + container_name = "tfstate" + key = "testinstance.tfstate" + use_azuread_auth = true + subscription_id = "26ad903f-2330-429d-8389-864ac35c4350" + } +} diff --git a/dev/testinstance/imports.tf b/dev/testinstance/imports.tf new file mode 100644 index 0000000..be39b1d --- /dev/null +++ b/dev/testinstance/imports.tf @@ -0,0 +1,6 @@ +# # Uncomment the following lines to import the resource group when Arc servers are provisioned by yourself. + +# import { +# id = "/subscriptions//resourceGroups/" +# to = module.base.azurerm_resource_group.rg +# } diff --git a/dev/testinstance/main.tf b/dev/testinstance/main.tf new file mode 100644 index 0000000..ece301f --- /dev/null +++ b/dev/testinstance/main.tf @@ -0,0 +1,6 @@ +module "base" { + source = "../../modules/base" + location = "eastus" + siteId = basename(abspath(path.module)) + domainFqdn = "jumpstart.local" +} \ No newline at end of file diff --git a/dev/testinstance/provider.tf b/dev/testinstance/provider.tf new file mode 100644 index 0000000..ea131dc --- /dev/null +++ b/dev/testinstance/provider.tf @@ -0,0 +1,9 @@ +provider "azurerm" { + features { + } + subscription_id = var.subscription_id +} + +provider "azapi" { + subscription_id = var.subscription_id +} diff --git a/dev/testinstance/terraform.tf b/dev/testinstance/terraform.tf new file mode 100644 index 0000000..c158c63 --- /dev/null +++ b/dev/testinstance/terraform.tf @@ -0,0 +1,15 @@ +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~>3.0" + } + random = { + source = "hashicorp/random" + version = "~>3.0" + } + azapi = { + source = "azure/azapi" + } + } +} diff --git a/dev/testinstance/variables.tf b/dev/testinstance/variables.tf new file mode 100644 index 0000000..9d38a1b --- /dev/null +++ b/dev/testinstance/variables.tf @@ -0,0 +1,66 @@ +variable "subscription_id" { + description = "The subscription id to register this environment." + type = string +} + +variable "local_admin_user" { + description = "The username of the local administrator account." + sensitive = true + type = string +} + +variable "local_admin_password" { + description = "The password of the local administrator account." + sensitive = true + type = string +} + +variable "domain_admin_user" { + description = "The username of the domain account." + sensitive = true + type = string +} + +variable "domain_admin_password" { + description = "The password of the domain account." + sensitive = true + type = string +} + +variable "deployment_user_password" { + sensitive = true + type = string + description = "The password for deployment user." +} + +variable "service_principal_id" { + description = "The id of service principal to create hci cluster." + sensitive = true + type = string +} + +variable "service_principal_secret" { + description = "The secret of service principal to create hci cluster." + sensitive = true + type = string +} + +variable "rp_service_principal_object_id" { + default = "" + type = string + description = "The object ID of the HCI resource provider service principal." +} + +variable "vm_admin_password" { + description = "Admin password for the VM" + type = string + sensitive = true + default = "" +} + +variable "domain_join_password" { + description = "Password of User with permissions to join the domain." + type = string + sensitive = true + default = "" +} diff --git a/modules/e2etest/checks.tf b/modules/e2etest/checks.tf new file mode 100644 index 0000000..acc3a2d --- /dev/null +++ b/modules/e2etest/checks.tf @@ -0,0 +1,16 @@ +locals { + is_windows = length(regexall("^[a-z]:", lower(abspath(path.root)))) > 0 + program = local.is_windows ? "powershell.exe" : "pwsh" +} + +data "external" "lnet_ip_check" { + program = [local.program, "-File", "${abspath(path.module)}/scripts/ip-range-overlap.ps1", var.starting_address, var.ending_address, var.lnet_starting_address, var.lnet_ending_address] + + lifecycle { + postcondition { + condition = self.result.result == "ok" + error_message = "AKS Arc IP range overlaps with HCI IP range." + } + } +} + diff --git a/modules/e2etest/main.tf b/modules/e2etest/main.tf new file mode 100644 index 0000000..2bf0135 --- /dev/null +++ b/modules/e2etest/main.tf @@ -0,0 +1,195 @@ +resource "azurerm_resource_group" "rg" { + depends_on = [ + data.external.lnet_ip_check + ] + name = local.resource_group_name + location = var.location + tags = { + siteId = var.site_id + } + + lifecycle { + ignore_changes = [tags] + } +} + +data "azurerm_client_config" "current" {} + +module "edge_site" { + source = "Azure/avm-res-edge-site/azurerm" + version = "~>0.0" + + location = azurerm_resource_group.rg.location + address_resource_name = local.address_resource_name + country = var.country + resource_group_id = azurerm_resource_group.rg.id + site_display_name = local.site_display_name + site_resource_name = local.site_resource_name + enable_telemetry = var.enable_telemetry +} + +# Prepare AD +module "hci_ad_provisioner" { + source = "Azure/avm-ptn-hci-ad-provisioner/azurerm" + version = "~>0.0" + + count = var.enable_provisioners ? 1 : 0 + resource_group_name = azurerm_resource_group.rg.name + + enable_telemetry = var.enable_telemetry # see variables.tf + # Beginning of specific varible for virtual environment + dc_port = var.dc_port + dc_ip = var.dc_ip + authentication_method = var.authentication_method + domain_fqdn = var.domain_fqdn + deployment_user_password = var.deployment_user_password + domain_admin_user = var.domain_admin_user + domain_admin_password = var.domain_admin_password + deployment_user = local.deployment_user_name + adou_path = local.adou_path +} + +# Prepare arc server +module "hci_server_provisioner" { + source = "Azure/avm-ptn-hci-server-provisioner/azurerm" + version = "~>0.0" + + for_each = var.enable_provisioners ? { + for index, server in var.servers : + server.name => server.ipv4Address + } : {} + + enable_telemetry = var.enable_telemetry # see variables.tf + name = each.key + resource_group_name = azurerm_resource_group.rg.name + local_admin_user = var.local_admin_user + local_admin_password = var.local_admin_password + authentication_method = var.authentication_method + server_ip = var.virtual_host_ip == "" ? each.value : var.virtual_host_ip + winrm_port = var.virtual_host_ip == "" ? 5985 : var.server_ports[each.key] + subscription_id = var.subscription_id + location = azurerm_resource_group.rg.location + tenant = data.azurerm_client_config.current.tenant_id + service_principal_id = var.service_principal_id + service_principal_secret = var.service_principal_secret + expand_c = var.virtual_host_ip == "" ? false : true +} + +module "azurestackhci_cluster" { + source = "Azure/avm-res-azurestackhci-cluster/azurerm" + version = "~>0.0" + + depends_on = [module.hci_server_provisioner, module.hci_ad_provisioner] + + location = azurerm_resource_group.rg.location + name = local.cluster_name + resource_group_name = azurerm_resource_group.rg.name + + enable_telemetry = var.enable_telemetry # see variables.tf + + site_id = var.site_id + domain_fqdn = var.domain_fqdn + starting_address = var.starting_address + ending_address = var.ending_address + subnet_mask = var.subnet_mask + default_gateway = var.default_gateway + dns_servers = var.dns_servers + adou_path = local.adou_path + servers = var.servers + management_adapters = var.management_adapters + storage_networks = var.storage_networks + rdma_enabled = var.rdma_enabled + storage_connectivity_switchless = var.storage_connectivity_switchless + custom_location_name = local.custom_location_name + witness_storage_account_name = local.witness_storage_account_name + keyvault_name = local.keyvault_name + random_suffix = local.random_suffix + deployment_user = local.deployment_user_name + deployment_user_password = var.deployment_user_password + local_admin_user = var.local_admin_user + local_admin_password = var.local_admin_password + service_principal_id = var.service_principal_id + service_principal_secret = var.service_principal_secret + rp_service_principal_object_id = var.rp_service_principal_object_id +} + +module "azurestackhci_logicalnetwork" { + source = "Azure/avm-res-azurestackhci-logicalnetwork/azurerm" + version = "~>0.0" + + depends_on = [module.azurestackhci_cluster] + + location = azurerm_resource_group.rg.location + name = local.logical_network_name + resource_group_name = azurerm_resource_group.rg.name + + enable_telemetry = var.enable_telemetry # see variables.tf + resource_group_id = azurerm_resource_group.rg.id + custom_location_id = module.azurestackhci_cluster.customlocation.id + vm_switch_name = module.azurestackhci_cluster.v_switch_name + starting_address = var.lnet_starting_address + ending_address = var.lnet_ending_address + dns_servers = length(var.lnet_dns_servers) == 0 ? var.dns_servers : var.lnet_dns_servers + default_gateway = var.lnet_default_gateway == "" ? var.default_gateway : var.lnet_default_gateway + address_prefix = var.lnet_address_prefix + vlan_id = var.lnet_vlan_id +} + +module "hybridcontainerservice_provisionedclusterinstance" { + source = "Azure/avm-res-hybridcontainerservice-provisionedclusterinstance/azurerm" + version = "~>0.0" + + depends_on = [module.azurestackhci_cluster, module.azurestackhci_logicalnetwork] + + location = azurerm_resource_group.rg.location + name = local.aks_arc_name + resource_group_name = azurerm_resource_group.rg.name + + enable_telemetry = var.enable_telemetry # see variables.tf + + custom_location_id = module.azurestackhci_cluster.customlocation.id + logical_network_id = module.azurestackhci_logicalnetwork.resource_id + agent_pool_profiles = var.agent_pool_profiles + ssh_key_vault_id = module.azurestackhci_cluster.keyvault.id + control_plane_ip = var.aks_arc_control_plane_ip + kubernetes_version = var.kubernetes_version + control_plane_count = var.control_plane_count + rbac_admin_group_object_ids = var.rbac_admin_group_object_ids +} + +locals { + server_names = [for server in var.servers : server.name] +} + +module "azuremonitorwindowsagent" { + source = "Azure/avm-ptn-azuremonitorwindowsagent/azurerm" + version = "~>0.0" + + depends_on = [module.azurestackhci_cluster] + enable_telemetry = var.enable_telemetry + + count = var.enable_insights ? 1 : 0 + resource_group_name = azurerm_resource_group.rg.name + server_names = local.server_names + arc_setting_id = module.azurestackhci_cluster.arc_settings.id + data_collection_rule_resource_id = var.data_collection_rule_resource_id +} + +resource "azapi_resource" "alerts" { + depends_on = [module.azurestackhci_cluster] + count = var.enable_alerts && var.enable_insights ? 1 : 0 + type = "Microsoft.AzureStackHCI/clusters/ArcSettings/Extensions@2023-08-01" + parent_id = module.azurestackhci_cluster.arc_settings.id + name = "AzureEdgeAlerts" + body = { + properties = { + extensionParameters = { + enableAutomaticUpgrade = true + autoUpgradeMinorVersion = false + publisher = "Microsoft.AzureStack.HCI.Alerts" + type = "AlertsForWindowsHCI" + settings = {} + } + } + } +} diff --git a/modules/e2etest/naming.tf b/modules/e2etest/naming.tf new file mode 100644 index 0000000..a489570 --- /dev/null +++ b/modules/e2etest/naming.tf @@ -0,0 +1,21 @@ +locals { + resource_group_name = "${var.site_id}-rg" + site_resource_name = length(var.site_id) < 4 ? "${var.site_id}-site" : "${var.site_id}" + site_display_name = var.site_id + address_resource_name = "${var.site_id}-address" + deployment_user_name = "${var.site_id}deploy" + witness_storage_account_name = "${lower(var.site_id)}wit" + keyvault_name = "${var.site_id}-kv" + adou_path = "OU=${var.site_id},${var.adou_suffix}" + cluster_name = "${var.site_id}-cl" + custom_location_name = "${var.site_id}-customlocation" + workspace_name = "${var.site_id}-workspace" + data_collection_endpoint_name = "${var.site_id}-dce" + data_collection_rule_name = "AzureStackHCI-${var.site_id}-dcr" + logical_network_name = "${var.site_id}-logicalnetwork" + aks_arc_name = "${var.site_id}-aksArc" + vm_name = "${var.site_id}-vm" + vm_admin_username = "${var.site_id}admin" + domain_join_user_name = "${var.site_id}vmuser" + random_suffix = true +} diff --git a/modules/e2etest/scripts/ip-range-overlap.ps1 b/modules/e2etest/scripts/ip-range-overlap.ps1 new file mode 100644 index 0000000..6d1e6de --- /dev/null +++ b/modules/e2etest/scripts/ip-range-overlap.ps1 @@ -0,0 +1,25 @@ +param( + $range1_start, + $range1_end, + $range2_start, + $range2_end +) + +$script:ErrorActionPreference = 'Stop' +$result = "overlap" + +if (([IPAddress]$range1_start).Address -gt ([IPAddress]$range1_end).Address -or ([IPAddress]$range2_start).Address -gt ([IPAddress]$range2_end).Address) { + $result = "invalid" +} + +if (([IPAddress]$range1_end).Address -lt ([IPAddress]$range2_start).Address) { + $result = "ok" +} + +if (([IPAddress]$range2_end).Address -lt ([IPAddress]$range1_start).Address) { + $result = "ok" +} + +echo @{ + "result"= $result +} | ConvertTo-Json diff --git a/modules/e2etest/terraform.tf b/modules/e2etest/terraform.tf new file mode 100644 index 0000000..30c50b7 --- /dev/null +++ b/modules/e2etest/terraform.tf @@ -0,0 +1,15 @@ +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~>3.0" + } + random = { + source = "hashicorp/random" + version = "~>3.0" + } + azapi = { + source = "azure/azapi" + } + } +} diff --git a/modules/e2etest/variables.aks-arc.global.tf b/modules/e2etest/variables.aks-arc.global.tf new file mode 100644 index 0000000..083bf0b --- /dev/null +++ b/modules/e2etest/variables.aks-arc.global.tf @@ -0,0 +1,37 @@ +variable "kubernetes_version" { + description = "The version of Kubernetes to use for the provisioned cluster." + type = string + default = "1.28.5" +} + +variable "control_plane_count" { + description = "The number of control plane nodes for the Kubernetes cluster." + type = number + default = 1 +} + +variable "agent_pool_profiles" { + description = "The agent pool profiles for the Kubernetes cluster." + type = list(object({ + count = number + enableAutoScaling = optional(bool, false) + nodeTaints = optional(list(string)) + nodeLabels = optional(map(string)) + maxPods = optional(number) + name = optional(string) + osSKU = optional(string, "CBLMariner") + osType = optional(string, "Linux") + vmSize = optional(string) + })) + default = [{ + count = 1 + enableAutoScaling = false + }] +} + +variable "rbac_admin_group_object_ids" { + description = "The object id of the Azure AD group that will be assigned the 'cluster-admin' role in the Kubernetes cluster." + type = list(string) + # Add your default admin groups here. Refer to the documentation under doc/AKS-Arc-Admin-Groups.md for more information. + # default = [""] +} diff --git a/modules/e2etest/variables.aks-arc.misc.tf b/modules/e2etest/variables.aks-arc.misc.tf new file mode 100644 index 0000000..9e4d2bb --- /dev/null +++ b/modules/e2etest/variables.aks-arc.misc.tf @@ -0,0 +1,2 @@ +# Reference variables +# variable "tenant" "ref/hci/tenant" diff --git a/modules/e2etest/variables.aks-arc.site.tf b/modules/e2etest/variables.aks-arc.site.tf new file mode 100644 index 0000000..64b3e84 --- /dev/null +++ b/modules/e2etest/variables.aks-arc.site.tf @@ -0,0 +1,4 @@ +variable "aks_arc_control_plane_ip" { + type = string + description = "The IP address of the control plane." +} diff --git a/modules/e2etest/variables.hci-extensions.global.tf b/modules/e2etest/variables.hci-extensions.global.tf new file mode 100644 index 0000000..f4db84d --- /dev/null +++ b/modules/e2etest/variables.hci-extensions.global.tf @@ -0,0 +1,17 @@ +variable "enable_insights" { + description = "Whether to enable Azure Monitor Insights." + type = bool + default = false +} + +variable "enable_alerts" { + description = "Whether to enable Azure Monitor Alerts." + type = bool + default = false +} + +variable "data_collection_rule_resource_id" { + type = string + description = "The id of the Azure Log Analytics data collection rule." + default = "" +} diff --git a/modules/e2etest/variables.hci-extensions.misc.tf b/modules/e2etest/variables.hci-extensions.misc.tf new file mode 100644 index 0000000..8baca09 --- /dev/null +++ b/modules/e2etest/variables.hci-extensions.misc.tf @@ -0,0 +1,3 @@ +# Reference variables +# variable "siteId" "ref/main/siteId" +# variable "serverNames" "ref/hci/servers" "serverNames = [for server in var.servers : server.name]" diff --git a/modules/e2etest/variables.hci-provisioners.global.tf b/modules/e2etest/variables.hci-provisioners.global.tf new file mode 100644 index 0000000..b5ff508 --- /dev/null +++ b/modules/e2etest/variables.hci-provisioners.global.tf @@ -0,0 +1,26 @@ +variable "enable_provisioners" { + type = bool + default = true + description = "Whether to enable provisioners." +} + +variable "dc_ip" { + type = string + description = "The ip of the server." +} + +variable "destory_adou" { + description = "whether destroy previous adou" + default = false + type = bool +} + +variable "authentication_method" { + type = string + description = "The authentication method for Enter-PSSession." + validation { + condition = can(regex("^(Default|Basic|Negotiate|NegotiateWithImplicitCredential|Credssp|Digest|Kerberos)$", var.authentication_method)) + error_message = "Value of authenticationMethod should be {Default | Basic | Negotiate | NegotiateWithImplicitCredential | Credssp | Digest | Kerberos}" + } + default = "Default" +} diff --git a/modules/e2etest/variables.hci-provisioners.misc.tf b/modules/e2etest/variables.hci-provisioners.misc.tf new file mode 100644 index 0000000..4f8a55c --- /dev/null +++ b/modules/e2etest/variables.hci-provisioners.misc.tf @@ -0,0 +1,47 @@ +# Pass through variables +variable "domain_admin_user" { + type = string + description = "The username for the domain administrator account." +} + +variable "domain_admin_password" { + # sensitive = true + type = string + description = "The password for the domain administrator account." +} + +# Virtual host related variables +variable "virtual_host_ip" { + type = string + description = "The virtual host IP address." + default = "" +} + +variable "dc_port" { + type = number + description = "Domain controller winrm port in virtual host" + default = 5985 +} + +variable "server_ports" { + type = map(number) + description = "Server winrm ports in virtual host" + default = {} +} + + +# Reference variables +# variable "location" "ref/main/location" +# variable "siteId" "ref/main/siteId" +# variable "siteName" "ref/main/siteName" +# variable "subscriptionId" "ref/main/subscriptionId" +# variable "servers" "ref/hci/servers" +# variable "deploymentUser" "ref/hci/deploymentUser" +# variable "deploymentUserPassword" "ref/hci/deploymentUserPassword" +# variable "localAdminUser" "ref/hci/localAdminUser" +# variable "localAdminPassword" "ref/hci/localAdminPassword" +# variable "domainFqdn" "ref/hci/domainFqdn" +# variable "adouPath" "ref/hci/adouPath" +# variable "tenant" "ref/hci/tenant" +# variable "servicePrincipalId" "ref/hci/servicePrincipalId" +# variable "servicePrincipalSecret" "ref/hci/servicePrincipalSecret" diff --git a/modules/e2etest/variables.hci.global.tf b/modules/e2etest/variables.hci.global.tf new file mode 100644 index 0000000..8c67b60 --- /dev/null +++ b/modules/e2etest/variables.hci.global.tf @@ -0,0 +1,47 @@ +variable "domain_fqdn" { + description = "The domain FQDN." + type = string +} + +variable "adou_suffix" { + type = string + description = "The suffix of Active Directory OU path." +} + +variable "subnet_mask" { + type = string + description = "The subnet mask for the network." + default = "255.255.255.0" +} + +variable "default_gateway" { + description = "The default gateway for the network." + type = string +} + +variable "dns_servers" { + type = list(string) + description = "A list of DNS server IP addresses." +} + +variable "management_adapters" { + type = list(string) +} + +variable "storage_networks" { + type = list(object({ + name = string + networkAdapterName = string + vlanId = string + })) +} + +variable "rdma_enabled" { + type = bool + description = "Indicates whether RDMA is enabled." +} + +variable "storage_connectivity_switchless" { + type = bool + description = "Indicates whether storage connectivity is switchless." +} diff --git a/modules/e2etest/variables.hci.misc.tf b/modules/e2etest/variables.hci.misc.tf new file mode 100644 index 0000000..5f12399 --- /dev/null +++ b/modules/e2etest/variables.hci.misc.tf @@ -0,0 +1,40 @@ +variable "rp_service_principal_object_id" { + default = "" + type = string + description = "The object ID of the HCI resource provider service principal." +} + +variable "deployment_user_password" { + sensitive = true + type = string + description = "The password for deployment user." +} + +variable "local_admin_user" { + type = string + description = "The username for the local administrator account." +} + +variable "local_admin_password" { + sensitive = true + type = string + description = "The password for the local administrator account." +} + +variable "service_principal_id" { + type = string + sensitive = true + description = "The service principal ID for ARB." +} + +variable "service_principal_secret" { + type = string + sensitive = true + description = "The service principal secret." +} + +# variable "location" "ref/main/location" +# variable "siteId" "ref/main/siteId" +# variable "siteName" "ref/main/siteName" +# variable "subscriptionId" "ref/main/subscriptionId" +# variable "deploymentUser" "ref/naming/deploymentUserName" diff --git a/modules/e2etest/variables.hci.site.tf b/modules/e2etest/variables.hci.site.tf new file mode 100644 index 0000000..a2191f5 --- /dev/null +++ b/modules/e2etest/variables.hci.site.tf @@ -0,0 +1,17 @@ +variable "servers" { + type = list(object({ + name = string + ipv4Address = string + })) + description = "A list of servers with their names and IPv4 addresses." +} + +variable "starting_address" { + description = "The starting IP address of the IP address range." + type = string +} + +variable "ending_address" { + description = "The ending IP address of the IP address range." + type = string +} diff --git a/modules/e2etest/variables.logical-network.global.tf b/modules/e2etest/variables.logical-network.global.tf new file mode 100644 index 0000000..7b5f822 --- /dev/null +++ b/modules/e2etest/variables.logical-network.global.tf @@ -0,0 +1,11 @@ +variable "lnet_dns_servers" { + type = list(string) + description = "A list of DNS server IP addresses." + default = [] +} + +variable "lnet_default_gateway" { + type = string + description = "The default gateway for the network." + default = "" +} diff --git a/modules/e2etest/variables.logical-network.site.tf b/modules/e2etest/variables.logical-network.site.tf new file mode 100644 index 0000000..5022915 --- /dev/null +++ b/modules/e2etest/variables.logical-network.site.tf @@ -0,0 +1,20 @@ +variable "lnet_address_prefix" { + type = string + description = "The CIDR prefix of the subnet that start from startting address and end with ending address, this can be omit if using existing logical network" +} + +variable "lnet_starting_address" { + type = string + description = "The starting IP address of the IP address range of the logical network, this can be omit if using existing logical network" +} + +variable "lnet_ending_address" { + type = string + description = "The ending IP address of the IP address range of the logical network, this can be omit if using existing logical network" +} + +variable "lnet_vlan_id" { + type = number + description = "The vlan id of the logical network, default is not set vlan id, this can be omit if using existing logical network" + default = null +} diff --git a/modules/e2etest/variables.main.global.tf b/modules/e2etest/variables.main.global.tf new file mode 100644 index 0000000..4a96eed --- /dev/null +++ b/modules/e2etest/variables.main.global.tf @@ -0,0 +1,14 @@ +variable "location" { + type = string + description = "The Azure region where the resources will be deployed." +} + +variable "enable_telemetry" { + type = bool + default = true + description = <. +If it is set to false, then no telemetry will be collected. +DESCRIPTION +} diff --git a/modules/e2etest/variables.main.misc.tf b/modules/e2etest/variables.main.misc.tf new file mode 100644 index 0000000..54c2106 --- /dev/null +++ b/modules/e2etest/variables.main.misc.tf @@ -0,0 +1,4 @@ +variable "subscription_id" { + type = string + description = "The subscription ID for resources." +} diff --git a/modules/e2etest/variables.main.site.tf b/modules/e2etest/variables.main.site.tf new file mode 100644 index 0000000..7068810 --- /dev/null +++ b/modules/e2etest/variables.main.site.tf @@ -0,0 +1,4 @@ +variable "site_id" { + type = string + description = "A unique identifier for the site." +} diff --git a/modules/e2etest/variables.site-manager.global.tf b/modules/e2etest/variables.site-manager.global.tf new file mode 100644 index 0000000..92bde5c --- /dev/null +++ b/modules/e2etest/variables.site-manager.global.tf @@ -0,0 +1,5 @@ +variable "country" { + description = "The order country of the site." + type = string + default = "" +} diff --git a/modules/e2etest/variables.site-manager.misc.tf b/modules/e2etest/variables.site-manager.misc.tf new file mode 100644 index 0000000..e4ac7d4 --- /dev/null +++ b/modules/e2etest/variables.site-manager.misc.tf @@ -0,0 +1 @@ +# variable "siteId" "ref/main/siteId" diff --git a/modules/e2etest/variables.site-manager.site.tf b/modules/e2etest/variables.site-manager.site.tf new file mode 100644 index 0000000..e36c8fd --- /dev/null +++ b/modules/e2etest/variables.site-manager.site.tf @@ -0,0 +1,77 @@ +variable "city" { + description = "The city of the site." + type = string + default = "" +} + +variable "company_name" { + description = "The company name of the site." + type = string + default = "" +} + +variable "postal_code" { + description = "The postal code of the site." + type = string + default = "" +} + +variable "state_or_province" { + description = "The state or province of the site." + type = string + default = "" +} + +variable "street_address_1" { + description = "The first line of the street address of the site." + type = string + default = "" +} + +variable "street_address_2" { + description = "The second line of the street address of the site." + type = string + default = "" +} + +variable "street_address_3" { + description = "The third line of the street address of the site." + type = string + default = "" +} + +variable "zip_extended_code" { + description = "The extended ZIP code of the site." + type = string + default = "" +} + +variable "contact_name" { + description = "The contact name of the site." + type = string + default = " " +} + +variable "email_list" { + description = "A list of email addresses for the site." + type = list(string) + default = [] +} + +variable "mobile" { + description = "The mobile phone number of the site." + type = string + default = "" +} + +variable "phone" { + description = "The phone number of the site." + type = string + default = "" +} + +variable "phone_extension" { + description = "The phone extension of the site." + type = string + default = "" +} diff --git a/prod/.gitignore b/prod/.gitignore new file mode 100644 index 0000000..6423733 --- /dev/null +++ b/prod/.gitignore @@ -0,0 +1,4 @@ + +*.tfstate +*.tfstate.backup +*.tfvars \ No newline at end of file diff --git a/qa/.gitignore b/qa/.gitignore new file mode 100644 index 0000000..6423733 --- /dev/null +++ b/qa/.gitignore @@ -0,0 +1,4 @@ + +*.tfstate +*.tfstate.backup +*.tfvars \ No newline at end of file