在 Azure Pipelines 生成中使用 Azure Repos git 模块源进行身份验证



我目前正在为 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 脚本来做类似的事情。

但是,如果存储库gitterraform模块中的模块在另一个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 存储库

最新更新