Skip to content

Commit 4e2e100

Browse files
Merge pull request #1412 from georgy-gorelko/main
rewire-pipelines: set PR trigger configuration after rewiring to GitHub based on Azure DevOps branch policy
2 parents b148ead + 97e5c39 commit 4e2e100

14 files changed

+1545
-256
lines changed

RELEASENOTES.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1+
- **ado2gh rewire-pipeline**: Migration now preserves all original trigger configurations (pullRequest, continuousIntegration, etc.) from Azure DevOps pipelines.
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
using System.Collections.Generic;
2+
using Newtonsoft.Json;
3+
4+
namespace Octoshift.Models;
5+
6+
/// <summary>
7+
/// Represents an Azure DevOps branch policy configuration
8+
/// </summary>
9+
public class AdoBranchPolicy
10+
{
11+
[JsonProperty("id")]
12+
public string Id { get; set; }
13+
14+
[JsonProperty("type")]
15+
public AdoPolicyType Type { get; set; }
16+
17+
[JsonProperty("isEnabled")]
18+
public bool IsEnabled { get; set; }
19+
20+
[JsonProperty("settings")]
21+
public AdoBranchPolicySettings Settings { get; set; }
22+
}
23+
24+
/// <summary>
25+
/// Represents the type information for an Azure DevOps policy
26+
/// </summary>
27+
public class AdoPolicyType
28+
{
29+
[JsonProperty("id")]
30+
public string Id { get; set; }
31+
32+
[JsonProperty("displayName")]
33+
public string DisplayName { get; set; }
34+
}
35+
36+
/// <summary>
37+
/// Represents the settings for an Azure DevOps branch policy
38+
/// </summary>
39+
public class AdoBranchPolicySettings
40+
{
41+
[JsonProperty("buildDefinitionId")]
42+
public string BuildDefinitionId { get; set; }
43+
44+
[JsonProperty("displayName")]
45+
public string DisplayName { get; set; }
46+
47+
[JsonProperty("queueOnSourceUpdateOnly")]
48+
public bool QueueOnSourceUpdateOnly { get; set; }
49+
50+
[JsonProperty("manualQueueOnly")]
51+
public bool ManualQueueOnly { get; set; }
52+
53+
[JsonProperty("validDuration")]
54+
public double ValidDuration { get; set; }
55+
}
56+
57+
/// <summary>
58+
/// Represents the response wrapper for Azure DevOps branch policies
59+
/// </summary>
60+
public class AdoBranchPolicyResponse
61+
{
62+
[JsonProperty("value")]
63+
public IReadOnlyList<AdoBranchPolicy> Value { get; set; }
64+
65+
[JsonProperty("count")]
66+
public int Count { get; set; }
67+
}

src/Octoshift/Services/AdoApi.cs

Lines changed: 26 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,28 @@ public AdoApi(AdoClient client, string adoServerUrl, OctoLogger log)
2525
_log = log;
2626
}
2727

28+
// Basic HTTP wrapper methods for use by other services
29+
public virtual async Task<string> GetAsync(string relativeUrl)
30+
{
31+
ArgumentNullException.ThrowIfNull(relativeUrl);
32+
var url = relativeUrl.StartsWith("http") ? relativeUrl : $"{_adoBaseUrl}{relativeUrl}";
33+
return await _client.GetAsync(url);
34+
}
35+
36+
public virtual async Task PutAsync(string relativeUrl, object payload)
37+
{
38+
ArgumentNullException.ThrowIfNull(relativeUrl);
39+
var url = relativeUrl.StartsWith("http") ? relativeUrl : $"{_adoBaseUrl}{relativeUrl}";
40+
await _client.PutAsync(url, payload);
41+
}
42+
43+
public virtual async Task<string> PostAsync(string relativeUrl, object payload)
44+
{
45+
ArgumentNullException.ThrowIfNull(relativeUrl);
46+
var url = relativeUrl.StartsWith("http") ? relativeUrl : $"{_adoBaseUrl}{relativeUrl}";
47+
return await _client.PostAsync(url, payload);
48+
}
49+
2850
public virtual async Task<string> GetOrgOwner(string org)
2951
{
3052
var url = $"{_adoBaseUrl}/{org.EscapeDataString()}/_apis/Contribution/HierarchyQuery?api-version=5.0-preview.1";
@@ -510,7 +532,7 @@ public virtual async Task ShareServiceConnection(string adoOrg, string adoTeamPr
510532
await _client.PatchAsync(url, payload);
511533
}
512534

513-
public virtual async Task<(string DefaultBranch, string Clean, string CheckoutSubmodules)> GetPipeline(string org, string teamProject, int pipelineId)
535+
public virtual async Task<(string DefaultBranch, string Clean, string CheckoutSubmodules, JToken Triggers)> GetPipeline(string org, string teamProject, int pipelineId)
514536
{
515537
var url = $"{_adoBaseUrl}/{org.EscapeDataString()}/{teamProject.EscapeDataString()}/_apis/build/definitions/{pipelineId}?api-version=6.0";
516538

@@ -530,80 +552,10 @@ public virtual async Task ShareServiceConnection(string adoOrg, string adoTeamPr
530552
var checkoutSubmodules = (string)data["repository"]["checkoutSubmodules"];
531553
checkoutSubmodules = checkoutSubmodules == null ? "null" : checkoutSubmodules.ToLower();
532554

533-
return (defaultBranch, clean, checkoutSubmodules);
534-
}
535-
536-
public virtual async Task ChangePipelineRepo(string adoOrg, string teamProject, int pipelineId, string defaultBranch, string clean, string checkoutSubmodules, string githubOrg, string githubRepo, string connectedServiceId, string targetApiUrl = null)
537-
{
538-
var url = $"{_adoBaseUrl}/{adoOrg.EscapeDataString()}/{teamProject.EscapeDataString()}/_apis/build/definitions/{pipelineId}?api-version=6.0";
539-
540-
var response = await _client.GetAsync(url);
541-
var data = JObject.Parse(response);
542-
543-
// Determine base URLs
544-
string apiUrl, webUrl, cloneUrl, branchesUrl, refsUrl, manageUrl;
545-
if (targetApiUrl.HasValue())
546-
{
547-
var apiUri = new Uri(targetApiUrl.TrimEnd('/'));
548-
var webHost = apiUri.Host.StartsWith("api.") ? apiUri.Host[4..] : apiUri.Host;
549-
var webScheme = apiUri.Scheme;
550-
var webBase = $"{webScheme}://{webHost}";
551-
apiUrl = $"{targetApiUrl.TrimEnd('/')}/repos/{githubOrg.EscapeDataString()}/{githubRepo.EscapeDataString()}";
552-
webUrl = $"{webBase}/{githubOrg.EscapeDataString()}/{githubRepo.EscapeDataString()}";
553-
cloneUrl = $"{webBase}/{githubOrg.EscapeDataString()}/{githubRepo.EscapeDataString()}.git";
554-
branchesUrl = $"{targetApiUrl.TrimEnd('/')}/repos/{githubOrg.EscapeDataString()}/{githubRepo.EscapeDataString()}/branches";
555-
refsUrl = $"{targetApiUrl.TrimEnd('/')}/repos/{githubOrg.EscapeDataString()}/{githubRepo.EscapeDataString()}/git/refs";
556-
manageUrl = webUrl;
557-
}
558-
else
559-
{
560-
apiUrl = $"https://api.github.com/repos/{githubOrg.EscapeDataString()}/{githubRepo.EscapeDataString()}";
561-
webUrl = $"https://github.com/{githubOrg.EscapeDataString()}/{githubRepo.EscapeDataString()}";
562-
cloneUrl = $"https://github.com/{githubOrg.EscapeDataString()}/{githubRepo.EscapeDataString()}.git";
563-
branchesUrl = $"https://api.github.com/repos/{githubOrg.EscapeDataString()}/{githubRepo.EscapeDataString()}/branches";
564-
refsUrl = $"https://api.github.com/repos/{githubOrg.EscapeDataString()}/{githubRepo.EscapeDataString()}/git/refs";
565-
manageUrl = webUrl;
566-
}
567-
568-
var newRepo = new
569-
{
570-
properties = new
571-
{
572-
apiUrl,
573-
branchesUrl,
574-
cloneUrl,
575-
connectedServiceId,
576-
defaultBranch,
577-
fullName = $"{githubOrg}/{githubRepo}",
578-
manageUrl,
579-
orgName = githubOrg,
580-
refsUrl,
581-
safeRepository = $"{githubOrg.EscapeDataString()}/{githubRepo.EscapeDataString()}",
582-
shortName = githubRepo,
583-
reportBuildStatus = true
584-
},
585-
id = $"{githubOrg}/{githubRepo}",
586-
type = "GitHub",
587-
name = $"{githubOrg}/{githubRepo}",
588-
url = cloneUrl,
589-
defaultBranch,
590-
clean,
591-
checkoutSubmodules
592-
};
593-
594-
var payload = new JObject();
595-
596-
foreach (var prop in data.Properties())
597-
{
598-
if (prop.Name == "repository")
599-
{
600-
prop.Value = JObject.Parse(newRepo.ToJson());
601-
}
602-
603-
payload.Add(prop.Name, prop.Value);
604-
}
555+
// Capture trigger information to preserve during rewiring
556+
var triggers = data["triggers"];
605557

606-
await _client.PutAsync(url, payload.ToObject(typeof(object)));
558+
return (defaultBranch, clean, checkoutSubmodules, triggers);
607559
}
608560

609561
public virtual async Task<string> GetBoardsGithubRepoId(string org, string teamProject, string teamProjectId, string endpointId, string githubOrg, string githubRepo)

0 commit comments

Comments
 (0)