0

I create 2 YAML pipelines for each of my repos in Azure DevOps using the REST API.

The pipeline works, except that it won't trigger on repo changes. Somehow the pipelines are not fully recognized be Azure DevOps. This means that even when the default azure-pipelines.yml file is used by a REST-created pipeline, the button "Set up build" is still displayed on the repo page.

Also, when I go to Pipelines to set up extra pipelines manually, I cannot do that. It wants to create a new pipeline for the azure-pipelines.yml file (even though it is already used by a REST-created pipeline) and does not allow me to pick a different .yml file.

Here is the code I use to create my pipelines with the REST API:

string pipelinesURL = $"https://dev.azure.com/{ViewModel.Organization}/{ViewModel.ProjectName}/_apis/pipelines?api-version=6.0-preview.1";

using (HttpClient client = new HttpClient())
            {
                client.DefaultRequestHeaders.Accept.Add(
                    new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));

                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic",
                    Convert.ToBase64String(
                        System.Text.ASCIIEncoding.ASCII.GetBytes(
                            string.Format("{0}:{1}", "", pat))));

                string folderToUse = "null";
                if (!string.IsNullOrEmpty(folder))
                {
                    folderToUse = "\"" + folder + "\""; //folder + "/"; // @"/" + folder;
                }

                string pipelineDef =
                "{" +
               $"   \"folder\": {folderToUse}," +
               $"   \"name\": \"{name}\"," +
                "   \"configuration\": {" +
                "       \"type\": \"yaml\"," +
               $"       \"path\": \"{yamlPath}\"," +
                "       \"repository\": {" +
               $"           \"id\": \"{repoId}\"," +
               $"           \"name\": \"{repoName}\"," +
                "           \"type\": \"azureReposGit\"" +
                "       }" +
                "   }" +
                "}";

                StringContent stringContent = new StringContent(pipelineDef, Encoding.UTF8, "application/json");

                string url = pipelinesURL;

                   
                using (HttpResponseMessage response = await client.PostAsync(
                        url, stringContent))
                {
                    response.EnsureSuccessStatusCode();
                    string responseBody = await response.Content.ReadAsStringAsync();
                    Console.WriteLine(responseBody);

                    return true;
                }
            }

How do I fix the problem?

Rye bread
  • 1,305
  • 2
  • 13
  • 36

2 Answers2

0

The code is not enough to create working pipeline.

Also, using the pipelines endpoint does not work. It has to be the build definitions endpoint:

https://dev.azure.com/{Organization}/{ProjectName}/_apis/build/definitions?api-version=6.0;

Microsoft on different forums recommends getting the defintion from a manually created pipeline, modifying it, and using that for creating new pipelines:

How to create new build pipeline using Azure DevOps REST API?

Rye bread
  • 1,305
  • 2
  • 13
  • 36
0

You can actually use the pipelines endpoint to create the pipeline. After that a pipeline confusingly becomes a "build definition".

Theres just one huge, ugly update endpoint for this. So after creating your pipeline grab the id (definitionId) that is returned in the response.

Make a get request:

var getDefinitionRequest = new HttpRequestMessage(HttpMethod.Get, $"{request.CollectionUri}build/definitions/{definitionId}");
            getDefinitionRequest.Headers.Authorization =
                new BasicAuthenticationHeaderValue(request.DevopsToken, request.DevopsToken);

            var definitionResponse = await _httpClient.SendAsync(getDefinitionRequest, cancellationToken);
            definitionResponse.EnsureSuccessStatusCode();

Then parse the json payload (Json.Net)

var responseObject = JObject.Parse(await definitionResponse.Content.ReadAsStringAsync(cancellationToken));

You can now modify it. To disable PR trigger override that seems to be the default do this:

//enable PRs
            var triggers = responseObject.SelectToken("triggers")!.Value<JArray>()!;
            triggers.Add(JObject.Parse(@"{
            ""settingsSourceType"": 2,
            ""branchFilters"": [
                ""+refs/heads/master""
            ],
            ""forks"": {
                ""enabled"": false,
                ""allowSecrets"": false,
                ""allowFullAccessToken"": false
            },
            ""pathFilters"": [],
            ""requireCommentsForNonTeamMembersOnly"": false,
            ""requireCommentsForNonTeamMemberAndNonContributors"": false,
            ""isCommentRequiredForPullRequest"": false,
            ""triggerType"": ""pullRequest""
        }"));

You can now push it back.

var updateDefinitionRequest = new HttpRequestMessage(HttpMethod.Put, $"{request.CollectionUri}build/definitions/{definitionId}?api-version=6.0");
            updateDefinitionRequest.Headers.Authorization =
                new BasicAuthenticationHeaderValue(request.DevopsToken, request.DevopsToken);

            updateDefinitionRequest.Content = new StringContent(responseObject.ToString(Formatting.None));
            updateDefinitionRequest.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");

            var updateResponse = await _httpClient.SendAsync(updateDefinitionRequest, cancellationToken);

            updateResponse.EnsureSuccessStatusCode();

You can also use the same approach to add variables to the pipeline:

var variables = responseObject.SelectToken("variables");
            JObject items;
            if (variables == null)
            {
                responseObject.Add(new JProperty("variables", items = new JObject()));
            }
            else
            {
                items = variables.Value<JObject>()!;
            }
            
            items["ContainerName"] = new JObject(new JProperty("value", new JValue(request.ContainerName)));
            items["Repository"] = new JObject(new JProperty("value", new JValue(request.RepositoryName)));
            items["OctopusProjectName"] = new JObject(new JProperty("value", new JValue(request.OctopusProjectName)));

Its a very tiresome API to use.

Sam
  • 1,725
  • 1
  • 17
  • 28