我目前正在为 Azure DevOps 创建一个管道,以验证 Terraform 配置并将其应用于不同的订阅。
我的地形配置使用模块,这些模块"托管"在与地形配置相同的 Azure DevOps 项目中的其他存储库中。
可悲的是,当我尝试执行terraform init
来获取这些模块时,管道任务"挂起"在那里等待凭据输入。
正如关于在脚本中运行 Git 命令的管道文档中所建议的那样,我尝试使用persistCredentials:true
属性添加一个checkout
步骤。
从我在任务日志中看到的内容(见下文),凭据信息专门添加到当前存储库中,不可用于其他存储库。
添加persistCredentials:true
时执行的命令
2018-10-22T14:06:54.4347764Z ##[command]git config http.https://my-org@dev.azure.com/my-org/my-project/_git/my-repo.extraheader "AUTHORIZATION: bearer ***"
地形初始化任务的输出
2018-10-22T14:09:24.1711473Z terraform init -input=false
2018-10-22T14:09:24.2761016Z Initializing modules...
2018-10-22T14:09:24.2783199Z - module.my-module
2018-10-22T14:09:24.2786455Z Getting source "git::https://my-org@dev.azure.com/my-org/my-project/_git/my-module-repo?ref=1.0.2"
如何设置 git 凭据以适用于其他存储库?
我遇到了同样的问题,我最终所做的是在地形配置中标记SYSTEM_ACCESSTOKEN
。我在 Azure DevOps 中使用了 Tokenzization 任务,其中__
前缀和后缀用于识别令牌并将其替换为实际变量(它是可自定义的,但我发现双下划线最适合不干扰我拥有的任何代码)
- task: qetza.replacetokens.replacetokens-task.replacetokens@3
displayName: 'Replace tokens'
inputs:
targetFiles: |
**/*.tfvars
**/*.tf
tokenPrefix: '__'
tokenSuffix: '__'
如果您无法为 DevOps 组织安装自定义扩展,则类似find $(Build.SourcesDirectory)/ -type f -name 'main.tf' -exec sed -i 's~__SYSTEM_ACCESSTOKEN__~$(System.AccessToken)~g' {} ;
之类的东西也可以工作。
我的地形 main.tf 如下所示:
module "app" {
source = "git::https://token:__SYSTEM_ACCESSTOKEN__@dev.azure.com/actualOrgName/actualProjectName/_git/TerraformModules//azure/app-service?ref=__app-service-module-ver__"
....
}
它并不漂亮,但它可以完成工作。模块源(在撰写本文时)不支持来自 terraform 的变量输入。因此,我们可以做的是使用 Terrafile,它是一个开源项目,通过在代码旁边保留一个简单的 YAML 文件来帮助跟上您可能使用的模块和同一模块的不同版本。它似乎不再被积极维护,但它只是工作:https://github.com/coretech/terrafile 我的Terrafile示例:
app:
source: "https://token:__SYSTEM_ACCESSTOKEN__@dev.azure.com/actualOrgName/actualProjectName/_git/TerraformModules"
version: "feature/handle-twitter"
app-stable:
source: "https://token:__SYSTEM_ACCESSTOKEN__@dev.azure.com/actualOrgName/actualProjectName/_git/TerraformModules"
version: "1.0.5"
默认情况下,Terrafile 将模块下载到 ./vendor 目录,以便您可以将模块源代码指向类似以下内容的内容:
module "app" {
source = "./vendor/modules/app-stable/azure/app_service"
....
}
现在你只需要弄清楚如何在Terrafile所在的目录中执行terrafile
命令。 我的azure.pipelines.yml示例:
- script: curl -L https://github.com/coretech/terrafile/releases/download/v0.6/terrafile_0.6_Linux_x86_64.tar.gz | tar xz -C $(Agent.ToolsDirectory)
displayName: Install Terrafile
- script: |
cd $(Build.Repository.LocalPath)
$(Agent.ToolsDirectory)/terrafile
displayName: Download required modules
你基本上有两种方法可以做到这一点。
先决条件
请确保阅读并根据需要应用"在脚本中运行 Git 命令"文档中的启用脚本以运行 Git 命令部分。
解决方案#1:在管道运行时动态插入System.AccessToken
(或PAT,但我不建议这样做)
您可以通过以下方式执行此操作:
在- 代码中插入替换令牌,例如
__SYSTEM_ACCESSTOKEN__
(如 Nilsas 建议的那样),并使用一些令牌替换代码或qetza.replacetokens.replacetokens-task.replacetokens
任务插入值。此解决方案的缺点是,在本地运行terraform
时,还必须替换令牌。 - 使用一些代码将所有
git::https://dev.azure.com
文本替换为git::https://YOUR_ACCESS_TOKEN@dev.azure.com
。
我使用以下 bash 任务脚本使用了第二种方法(它搜索terragrunt
文件,但您可以适应terraform
文件而无需太多更改):
- bash: |
find $(Build.SourcesDirectory)/ -type f -name 'terragrunt.hcl' -exec sed -i 's~git::https://dev.azure.com~git::https://$(System.AccessToken)@dev.azure.com~g' {} ;
Abu Belai 提供了一个 PowerShell 脚本来做类似的事情。
但是,如果存储库git
terraform
模块中的模块在另一个git
存储库中称自己为模块,则这种类型的解决方案不起作用。
解决方案 #2:在存储库terraform
模块的 URLextraheader
全局添加访问令牌git
这样,所有模块的存储库(由代码直接调用或由被调用模块的代码间接调用)将能够使用访问令牌。我通过在terraform
/terragrunt
呼叫之前添加以下步骤来做到这一点:
- bash: |
git config --global http.https://dev.azure.com/<your-org>/<your-first-repo-project>/_git/<your-first-repo>.extraheader "AUTHORIZATION: bearer $(System.AccessToken)"
git config --global http.https://dev.azure.com/<your-org>/<your-second-repo-project>/_git/<your-second-repo>.extraheader "AUTHORIZATION: bearer $(System.AccessToken)"
您需要为每个被调用的git
存储库设置extraheader
。
请注意,如果管道在同一辅助角色上多次设置extraheader
,则可能需要在terraform
调用后取消设置extraheader
。这是因为git
可能会与多个extraheader
声明混淆。您可以通过添加到以下步骤来执行此操作:
- bash: |
git config --global --unset-all http.https://dev.azure.com/<your-org>/<your-first-repo-project>/_git/<your-first-repo>.extraheader
git config --global --unset-all http.https://dev.azure.com/<your-org>/<your-second-repo-project>/_git/<your-second-repo>.extraheader
我这样做了
_ado_token.ps1
# used in Azure DevOps to allow terrform to auth with Azure DevOps GIT repos
$tfmodules = Get-ChildItem $PSScriptRoot -Recurse -Filter "*.tf"
foreach ($tfmodule in $tfmodules) {
$content = [System.IO.File]::ReadAllText($tfmodule.FullName).Replace("git::https://myorg@","git::https://" + $env:SYSTEM_ACCESSTOKEN +"@")
[System.IO.File]::WriteAllText($tfmodule.FullName, $content)
}
azure-pipelines.yml
- task: PowerShell@2
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
inputs:
filePath: '_ado_token.ps1'
pwsh: true
displayName: '_ado_token.ps1'
我通过创建运行内联 powershell 脚本的管道模板解决了这个问题。然后,我将模板作为管道模板拉入,当使用任何 terraform 模块形成不同的存储库时,这是一个"资源"。 该脚本将对所有 .tf 文件执行递归搜索。然后使用正则表达式更新所有模块源 URL。
我选择了 REGEX 而不是标记模块 url,因为这将确保可以在开发机器上拉入模块,而无需对源代码进行任何更改。
parameters:
- name: terraform_directory
type: string
steps:
- task: PowerShell@2
displayName: Tokenize TF-Module Sources
env:
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
inputs:
targetType: 'inline'
script: |
$regex = "https://*(.+)dev.azure.com"
$tokenized_url = "https://token:$($env:SYSTEM_ACCESSTOKEN)@dev.azure.com"
Write-Host "Recursive Search in ${{ parameters.terraform_directory }}"
$tffiles = Get-ChildItem -Path "${{ parameters.terraform_directory }}" -Filter "*main.tf" -Recurse -Force
Write-Host "Found $($tffiles.Count) files ending with 'main.tf'"
if ($tffiles) { Write-Host $tffiles }
$tffiles | % {
Write-Host "Updating file $($_.FullName)"
$content = Get-Content $_.FullName
Write-Host "Replace Strings: $($content | Select-String -Pattern $regex)"
$content -replace $regex, $tokenized_url | Set-Content $_.FullName -Force
Write-Host "Updated content"
Write-Host (Get-Content $_.FullName)
}
据我所知,执行此操作的最佳方法与任何其他 Git 提供程序完全相同。只有对于Azure DevOps,我才遇到过extraheader
的方法。我一直使用它,并且在其他建议的方法无法获得令人满意的结果后,我又回到了它:
- script: |
MY_TOKEN=foobar
git config --global url."https://${MY_TOKEN}@dev.azure.com".insteadOf "https://dev.azure.com"
这是一个场景:
一个项目中有两个 git 存储库。
Repo1
包含地形模块代码。
Repo2
包含指向Repo1
(具有特定标记(如1.0.0
)作为模块源的地形代码。
您的管道 yaml 处于Repo2
中。
那么这就是你的Repo2
模块块的样子:
module "alz" {
source = "git::https://dev.azure.com/<org>/<project>/_git/Repo1?ref=<tag>"
.
.
}
在 Azure 管道中,
您将首先声明Repo1
的存储库资源:
resources:
repositories:
- repository: Repo1
type: git
name: "<project>/Repo1"
然后签出管道中的两个存储库,但保留 repo2 的凭据。
# Checkout Repo2
- checkout : self
# Checkout Repo1
- checkout : Repo1
persistCredentials: 'true'
即使您避免行persistCredentials: 'true'
,也请确保签出两个存储库。如果您只是结帐self
,即仅 Repo2,它可能不适用于从 Repo1 获取并会给出错误:
│ remote: TF401019: The Git repository with name or identifier
│ Repo1 does not exist or you do not have
│ permissions for the operation you are attempting.
现在设置凭据,即在terraform init
之前设置System.AccessToken。您可以通过两种方式执行此操作。
选项 1:
- bash: |
git config --global http.https://dev.azure.com/<org>/<project>/_git/Repo1.extraheader "AUTHORIZATION: bearer $(System.AccessToken)"
displayName: Set Credential
# Terraform Init
- task: TerraformTaskV4@4
inputs:
provider: 'azurerm'
command: 'init'
workingDirectory: '$(working_dir)'
backendServiceArm: $(sc_tf_backend)
backendAzureRmResourceGroupName: $(backendRGName)
backendAzureRmStorageAccountName: $(backendSAName)
backendAzureRmContainerName: $(backendContainerName)
backendAzureRmKey: $(backendBlobKey)
选项 2:
- bash: |
MY_TOKEN=$(System.AccessToken)
git config --global url."https://${MY_TOKEN}@dev.azure.com".insteadOf "https://dev.azure.com"
displayName: Set Credential
# Terraform Init
- task: TerraformTaskV4@4
inputs:
provider: 'azurerm'
command: 'init'
workingDirectory: '$(working_dir)'
backendServiceArm: $(sc_tf_backend)
backendAzureRmResourceGroupName: $(backendRGName)
backendAzureRmStorageAccountName: $(backendSAName)
backendAzureRmContainerName: $(backendContainerName)
backendAzureRmKey: $(backendBlobKey)
我认为你不能。通常,创建另一个生成并链接到该生成中的项目,以便在当前定义中使用它。这样你就不需要连接到不同的 Git 存储库