Azure DevOps create build definition via REST API given existing YAML

拈花ヽ惹草 提交于 2021-02-07 04:14:40

问题


I have over 100 YAML files within an existing Git repo, each defining their own build pipeline. I am trying to create a PowerShell script to create these build definitions so I don't have to spend hours using the web UI to manually add new build definitions and point to their respective YAML files.

I've come across similar questions and resources, but haven't been able to get this script to work.

  • How to create Build Definitions through VSTS REST API
  • Create Build Definition using Azure Devops API
  • https://www.nebbiatech.com/2018/11/29/automating-build-pipeline-creation-using-azure-devops-services-rest-api/

I know the REST API documentation supports cloning, but does it support creating a build definition that links to a YAML file within the Git repo?

$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;

I get the following error with the script above:

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

Is it possible to create a new build definition without cloning an existing one I have to manually create via the web UI?

I have the 100+ YAML files located in a folder /azure-pipeline-YAML/ inside the Git repo. I suspect I need to somehow include that in the JSON I send via the REST API, but where/how? I'm stuck at this definition.Process error.

Update

Thanks to @danielmann I ended up needing to get some additional info (i.e. repository.Id and changing the repository.Type). I put the following in the script to get an example of a temporary definition I created based on an existing YAML file.

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

The working script ended up being:

$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;

回答1:


It's failing when you specify process = 0 because process shouldn't be a numeric data type. Process needs to specify a YAML file and a "type" parameter.

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

I honestly forget what type "2" is versus type "1" versus type "90072972", but I've used that in the past.

The easiest way to figure this kind of thing out is to create a YAML build and pull down the definition JSON using the REST API. That's how I figured it out.




回答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:


Working off of @E-rich's solution, I was able to build a series of API calls that will create a pipeline with CI triggering enabled, as that was not working with the shown example.

Based on my testing, it was necessary to define 'queue' and 'triggers' as part of the POST when creating the build definition.

My completed script is below.

$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


来源:https://stackoverflow.com/questions/59058254/azure-devops-create-build-definition-via-rest-api-given-existing-yaml

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!