【问题标题】:Azure DevOps create build definition via REST API given existing YAMLAzure DevOps 通过给定现有 YAML 的 REST API 创建构建定义
【发布时间】:2019-11-26 19:37:27
【问题描述】:

我在现有的 Git 存储库中有 100 多个 YAML 文件,每个文件都定义了自己的构建管道。我正在尝试创建一个 PowerShell 脚本来创建这些构建定义,这样我就不必花费数小时使用 Web UI 来手动添加新的构建定义并指向它们各自的 YAML 文件。

我遇到过类似的问题和资源,但无法让此脚本正常工作。

我知道the REST API documentation 支持克隆,但它是否支持创建链接到 Git 存储库中的 YAML 文件的构建定义?

$organization = "my-company"
$project = "MyProject"
$projUrl = "https://dev.azure.com/$($organization)/$($project)/"
$patToken = "<PAT>"
$token = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($patToken)"))
$header = @{authorization = "Basic $token"}
$Url = "$($projUrl)_apis/build/definitions?api-version=5.1"

$json = @{
    project = "$($project)";
    name = "My.New.Definition.Linked.To.Existing.YAML.File";
    repository = @{
        url = "<the-https-link-to-my-Git-repo>";
    };
    # The script still fails with definition.Process cannot be null.
    # process = 0;
    path = "\A New Folder";
    type = "build"
}

$body = ($json | ConvertTo-Json -Depth 3)
Invoke-RestMethod -Uri $Url -Headers $header -Body $body -Method Post -ContentType application/json;

上面的脚本出现以下错误:

Invoke-RestMethod : {"$id":"1","innerException":null,"message":"Value cannot be null.\r\nParameter name:
definition.Process","typeName":"System.ArgumentNullException, mscorlib","typeKey":"ArgumentNullException","errorCode":0,"eventId":0}
At create_pipelines.ps1:22 char:1
+ Invoke-RestMethod -Uri $Url -Headers $header -Body $body -Method Post ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebException
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand

是否可以在不克隆我必须通过 Web UI 手动创建的现有定义的情况下创建新的构建定义?

我有 100 多个 YAML 文件位于 Git 存储库内的文件夹 /azure-pipeline-YAML/ 中。我怀疑我需要以某种方式将它包含在我通过 REST API 发送的 JSON 中,但是在哪里/如何?我被这个 definition.Process 错误困住了。

更新

感谢@danielmann,我最终需要获得一些额外的信息(即repository.Id 并更改repository.Type)。我将以下内容放入脚本中,以获取基于现有 YAML 文件创建的临时定义的示例。

$Url = "$($projUrl)_apis/build/definitions/13?api-version=5.1"
Invoke-RestMethod $Url -Headers $header -Method Get -ContentType application/json;

工作脚本最终是:

$organization = "my-company"
$project = "MyProject"
$projUrl = "https://dev.azure.com/$($organization)/$($project)/"
$patToken = "<PAT>"
$token = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($patToken)"))
$header = @{authorization = "Basic $token"}
$Url = "$($projUrl)_apis/build/definitions?api-version=5.1"

$json = @{
    project = "$($project)";
    name = "My.New.Definition.Linked.To.Existing.YAML.File";
    repository = @{
        url = "<the-https-link-to-my-Git-repo>";
        defaultBranch = "refs/heads/feature/my-new-feature-branch";
        id = "<taken-from-the-GET-API-request>";
        type = "TfsGit";
    };
    process = @{
        yamlFilename = "azure-pipeline-YAML/my-pipeline.yml";
        type = 2;
    };
    path = "\A New Folder";
    type = "build";
}

$body = ($json | ConvertTo-Json -Depth 3)
Invoke-RestMethod -Uri $Url -Headers $header -Body $body -Method Post -ContentType application/json;

【问题讨论】:

    标签: azure azure-devops azure-pipelines


    【解决方案1】:

    指定process = 0 时失败,因为process 不应该是数字数据类型。进程需要指定一个 YAML 文件和一个“类型”参数。

      "process": {
        "yamlFilename": "build.yaml",
        "type": 2
      }
    

    我真的忘记了“2”类型与“1”类型与“90072972”类型是什么,但我过去曾使用过。

    解决这种问题的最简单方法是创建一个 YAML 构建并使用 REST API 提取定义 JSON。我就是这么想的。

    【讨论】:

    • 当 Microsoft 懒得为这些 API 提供示例时,另一种解决问题的好方法是使用浏览器的 F12(开发人员)功能或 Telerik 的 Fiddler,同时在用户界面。
    • 不值得回答,但有人得到:{"$id":"1","innerException":null,"message":"Value cannot be null.\r\nParameter name: key","typeName":"System.ArgumentNullException, | mscorlib","typeKey":"ArgumentNullException","errorCode":0,"eventId":0}。没有key 参数。确保你有process.yamlFilename
    【解决方案2】:
         public class Constants
        {
         public class DevOps
            {
                public class Build
                {
                    public const string QUEUE_STATUS = "enabled";
                    public const string YAML_FILE_PATH = "Data/azure-pipelines.yml";
                    public const string AGENT_POOL_NAME = "Hosted Ubuntu 1604";
                    public const string JOB_AUTHORIZATION_SCOPE = "projectCollection";
                    public const string REPOSITORY_TYPE = "TfsGit";
                    public const string CREATE_BUILD_DEF_URI = "https://dev.azure.com/{0}/{1}/_apis/build/definitions?api-version=5.0";
                }
        }
    }
    public class Repository
    {
              public string id { get; set; }
            public string name { get; set; }
            public string url { get; set; }
            public DevOpsProject project { get; set; }
            public int size { get; set; }
            public string remoteUrl { get; set; }
            public string sshUrl { get; set; }
            public bool isFork { get; set; }
            public _Links _links { get; set; }
            public string defaultBranch { get; set; }
    
            public string type { get; set; }
    
    
    }
    
       private void CreateIntegrationPipelineWithForkRepository(string orgnizationName, string sourceProjectName, string sourceProjectId, Repository forkRepository)
        {
            System.Action action = delegate
            {
                //********************************* Create Build with CICD ******************///
                var continuousIntegration = new List<BuildTrigger>() { new BuildTrigger() };
                var referenceYamlPath = new
                {
                    yamlFilename = Constants.DevOps.Build.YAML_FILE_PATH,// = "Data/azure-pipelines.yml",
                    type = 2
                };
                var queueWithAgentPool = new
                {
                    name = Constants.DevOps.Build.AGENT_POOL_NAME,
                    pool = new
                    {
                        name = Constants.DevOps.Build.AGENT_POOL_NAME,
                        isHosted = true
                    }
                };
    
                //Set repository type to: 'TfsGit'
                forkRepository.type = Constants.DevOps.Build.REPOSITORY_TYPE;// = "TfsGit";
                var requestBuild = new
                {
                    triggers = continuousIntegration,
                    jobAuthorizationScope = Constants.DevOps.Build.JOB_AUTHORIZATION_SCOPE,
                    jobTimeoutInMinutes = 60,
                    jobCancelTimeoutInMinutes = 5,
                    process = referenceYamlPath,
                    repository = forkRepository, // new repository
                    quality = "definition",
                    queue = queueWithAgentPool,
                    name = string.Format("{0}-{1}", forkRepository.name, "ci"),
                    path = "\\",
                    type = "build",
                    queueStatus = Constants.DevOps.Build.QUEUE_STATUS,//= "enabled",
                    revision = 1,
                    project = new { id = sourceProjectId } // source project id
                };
    
                //********************************* Create Build with CICD ******************///
                var uri = string.Format(Constants.DevOps.Build.CREATE_BUILD_DEF_URI, orgnizationName,sourceProjectName);
    
                this.Post(uri, HttpMethod.Post, requestBuild);
            };
    
            _exceptionHandlingPolicy.Execute(action);
    
        }
        
        private string Post<T>(uri, HttpMethod.Post, T request)
        {
        if (request != null)
                    {
                        data = JsonConvert.SerializeObject(request);
                    }
                    var personalaccesstoken = "azure-devOps-PAT-token";
    
                    var authorization = Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(string.Format("{0}:{1}", "", personalaccesstoken)));
    
                    var result = _httpClient.Send(uri, method, data, authorization);
        }
    

    【讨论】:

      【解决方案3】:

      使用 @E-rich 的解决方案,我能够构建一系列 API 调用,这些调用将创建一个启用 CI 触发的管道,因为这不适用于所示示例。

      根据我的测试,在创建构建定义时,有必要将“队列”和“触发器”定义为 POST 的一部分。

      我完成的脚本如下。

      $DevOpsPat = "PersonalAccessTokenHere"
      $Organization = "OrgNameHere"
      $Project = "ProjNameHere"
      $RepoName = "RepoNameHere"
      $PipelineName = "PipelineNameHere"
      $PipelinePath = "\PathHere"
      $PipelineTriggerBranch = "master"
      $YamlPath = "/azure-pipelines.yml"
      
      $AuthHeader = 'Basic ' + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$($DevOpsPat)"))
      
      $QueuesSplat = @{
          Uri = "https://dev.azure.com/$Organization/$Project/_apis/distributedtask/queues?api-version=6.1-preview.1"
          Method = "GET"
          ContentType = "application/json"
          Headers = @{
              Authorization = $AuthHeader
          }
      }
      $Queues = Invoke-RestMethod @QueuesSplat
      $APQueueID = ($Queues.value | Where-Object {$_.name -eq "Azure Pipelines"}).id
      
      $ReposSplat = @{
          Uri = "https://dev.azure.com/$Organization/$Project/_apis/git/repositories?api-version=6.1-preview.1"
          Method = "GET"
          ContentType = "application/json"
          Headers = @{
              Authorization = $AuthHeader
          }
      }
      $Repos = Invoke-RestMethod @ReposSplat
      $Repo = $Repos.value | Where-Object {$_.name -eq $RepoName}
      
      $PipelineSplat = @{
          Uri = "https://dev.azure.com/$Organization/$Project/_apis/build/definitions?api-version=5.1"
          Method = "POST"
          ContentType = "application/json" 
          Body = ConvertTo-Json @{
              project = "$Project"
              name = $PipelineName
              repository = @{
                  url = $Repo.webUrl
                  defaultBranch = $PipelineTriggerBranch
                  id = $Repo.id
                  type = "TfsGit"
              }
              process = @{
                  yamlFilename = $YamlPath
                  type = 2
              }
              path = $PipelinePath
              type = "build"
              triggers = @(
                  @{
                      settingsSourceType = 2
                      triggerType = "continuousIntegration"
                  }
              )
              queue  = @{
                  id = $APQueueID
              }
          }
          Headers = @{
              Authorization = $AuthHeader
          }    
      }
      Invoke-RestMethod @PipelineSplat
      

      【讨论】:

        猜你喜欢
        • 2019-08-24
        • 1970-01-01
        • 2020-05-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-01-21
        • 1970-01-01
        相关资源
        最近更新 更多