Skip to content
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion RELEASENOTES.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@

- **ado2gh rewire-pipeline**: Migration now preserves all original trigger configurations (pullRequest, continuousIntegration, etc.) from Azure DevOps pipelines.
67 changes: 67 additions & 0 deletions src/Octoshift/Models/AdoBranchPolicy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System.Collections.Generic;
using Newtonsoft.Json;

namespace Octoshift.Models;

/// <summary>
/// Represents an Azure DevOps branch policy configuration
/// </summary>
public class AdoBranchPolicy
{
[JsonProperty("id")]
public string Id { get; set; }

[JsonProperty("type")]
public AdoPolicyType Type { get; set; }

[JsonProperty("isEnabled")]
public bool IsEnabled { get; set; }

[JsonProperty("settings")]
public AdoBranchPolicySettings Settings { get; set; }
}

/// <summary>
/// Represents the type information for an Azure DevOps policy
/// </summary>
public class AdoPolicyType
{
[JsonProperty("id")]
public string Id { get; set; }

[JsonProperty("displayName")]
public string DisplayName { get; set; }
}

/// <summary>
/// Represents the settings for an Azure DevOps branch policy
/// </summary>
public class AdoBranchPolicySettings
{
[JsonProperty("buildDefinitionId")]
public string BuildDefinitionId { get; set; }

[JsonProperty("displayName")]
public string DisplayName { get; set; }

[JsonProperty("queueOnSourceUpdateOnly")]
public bool QueueOnSourceUpdateOnly { get; set; }

[JsonProperty("manualQueueOnly")]
public bool ManualQueueOnly { get; set; }

[JsonProperty("validDuration")]
public double ValidDuration { get; set; }
}

/// <summary>
/// Represents the response wrapper for Azure DevOps branch policies
/// </summary>
public class AdoBranchPolicyResponse
{
[JsonProperty("value")]
public IReadOnlyList<AdoBranchPolicy> Value { get; set; }

[JsonProperty("count")]
public int Count { get; set; }
}
100 changes: 26 additions & 74 deletions src/Octoshift/Services/AdoApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,28 @@ public AdoApi(AdoClient client, string adoServerUrl, OctoLogger log)
_log = log;
}

// Basic HTTP wrapper methods for use by other services
public virtual async Task<string> GetAsync(string relativeUrl)
{
ArgumentNullException.ThrowIfNull(relativeUrl);
var url = relativeUrl.StartsWith("http") ? relativeUrl : $"{_adoBaseUrl}{relativeUrl}";
return await _client.GetAsync(url);
}

public virtual async Task PutAsync(string relativeUrl, object payload)
{
ArgumentNullException.ThrowIfNull(relativeUrl);
var url = relativeUrl.StartsWith("http") ? relativeUrl : $"{_adoBaseUrl}{relativeUrl}";
await _client.PutAsync(url, payload);
}

public virtual async Task<string> PostAsync(string relativeUrl, object payload)
{
ArgumentNullException.ThrowIfNull(relativeUrl);
var url = relativeUrl.StartsWith("http") ? relativeUrl : $"{_adoBaseUrl}{relativeUrl}";
return await _client.PostAsync(url, payload);
}

public virtual async Task<string> GetOrgOwner(string org)
{
var url = $"{_adoBaseUrl}/{org.EscapeDataString()}/_apis/Contribution/HierarchyQuery?api-version=5.0-preview.1";
Expand Down Expand Up @@ -510,7 +532,7 @@ public virtual async Task ShareServiceConnection(string adoOrg, string adoTeamPr
await _client.PatchAsync(url, payload);
}

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

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

return (defaultBranch, clean, checkoutSubmodules);
}

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)
{
var url = $"{_adoBaseUrl}/{adoOrg.EscapeDataString()}/{teamProject.EscapeDataString()}/_apis/build/definitions/{pipelineId}?api-version=6.0";

var response = await _client.GetAsync(url);
var data = JObject.Parse(response);

// Determine base URLs
string apiUrl, webUrl, cloneUrl, branchesUrl, refsUrl, manageUrl;
if (targetApiUrl.HasValue())
{
var apiUri = new Uri(targetApiUrl.TrimEnd('/'));
var webHost = apiUri.Host.StartsWith("api.") ? apiUri.Host[4..] : apiUri.Host;
var webScheme = apiUri.Scheme;
var webBase = $"{webScheme}://{webHost}";
apiUrl = $"{targetApiUrl.TrimEnd('/')}/repos/{githubOrg.EscapeDataString()}/{githubRepo.EscapeDataString()}";
webUrl = $"{webBase}/{githubOrg.EscapeDataString()}/{githubRepo.EscapeDataString()}";
cloneUrl = $"{webBase}/{githubOrg.EscapeDataString()}/{githubRepo.EscapeDataString()}.git";
branchesUrl = $"{targetApiUrl.TrimEnd('/')}/repos/{githubOrg.EscapeDataString()}/{githubRepo.EscapeDataString()}/branches";
refsUrl = $"{targetApiUrl.TrimEnd('/')}/repos/{githubOrg.EscapeDataString()}/{githubRepo.EscapeDataString()}/git/refs";
manageUrl = webUrl;
}
else
{
apiUrl = $"https://api.github.com/repos/{githubOrg.EscapeDataString()}/{githubRepo.EscapeDataString()}";
webUrl = $"https://github.com/{githubOrg.EscapeDataString()}/{githubRepo.EscapeDataString()}";
cloneUrl = $"https://github.com/{githubOrg.EscapeDataString()}/{githubRepo.EscapeDataString()}.git";
branchesUrl = $"https://api.github.com/repos/{githubOrg.EscapeDataString()}/{githubRepo.EscapeDataString()}/branches";
refsUrl = $"https://api.github.com/repos/{githubOrg.EscapeDataString()}/{githubRepo.EscapeDataString()}/git/refs";
manageUrl = webUrl;
}

var newRepo = new
{
properties = new
{
apiUrl,
branchesUrl,
cloneUrl,
connectedServiceId,
defaultBranch,
fullName = $"{githubOrg}/{githubRepo}",
manageUrl,
orgName = githubOrg,
refsUrl,
safeRepository = $"{githubOrg.EscapeDataString()}/{githubRepo.EscapeDataString()}",
shortName = githubRepo,
reportBuildStatus = true
},
id = $"{githubOrg}/{githubRepo}",
type = "GitHub",
name = $"{githubOrg}/{githubRepo}",
url = cloneUrl,
defaultBranch,
clean,
checkoutSubmodules
};

var payload = new JObject();

foreach (var prop in data.Properties())
{
if (prop.Name == "repository")
{
prop.Value = JObject.Parse(newRepo.ToJson());
}

payload.Add(prop.Name, prop.Value);
}
// Capture trigger information to preserve during rewiring
var triggers = data["triggers"];

await _client.PutAsync(url, payload.ToObject(typeof(object)));
return (defaultBranch, clean, checkoutSubmodules, triggers);
}

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