Skip to content

Commit

Permalink
Reorganized the repo and support for Node.js added
Browse files Browse the repository at this point in the history
  • Loading branch information
FlorianRappl committed May 17, 2019
1 parent f0ca59b commit c128ee9
Show file tree
Hide file tree
Showing 21 changed files with 334 additions and 114 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.vs/

DevOpsLittleHelper/Properties/

25 changes: 25 additions & 0 deletions DevOpsLittleHelper/Common/Constants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;

namespace DevOpsLittleHelper
{
internal class Constants
{
public static readonly string VsoGroup = Environment.GetEnvironmentVariable("DEVOPS_ORGA") ?? "your-name-here";

public static readonly string VsoApiRoot = $"https://{VsoGroup}.visualstudio.com";

public static readonly string NpmApiRoot = $"https://{VsoGroup}.pkgs.visualstudio.com/_packaging/NPM-Feed@Local/npm/registry";

public static readonly string NuGetApiRoot = $"https://{VsoGroup}.pkgs.visualstudio.com/_packaging/NuGet-Feed@Local/nuget";

public static readonly string NewBranchName = Environment.GetEnvironmentVariable("DEVOPS_NEW_BRANCH") ?? "feature/auto-ref-update";

public static readonly string NewPrTitle = Environment.GetEnvironmentVariable("DEVOPS_PR_TITLE") ?? "Automatic Reference Update ({packageName} v{packageVersion})";

public static readonly string NewPrDescription = Environment.GetEnvironmentVariable("DEVOPS_PR_DESC") ?? "Updated the reference / automatic job.\n\nPowered by Azure DevOps Little Helper v{appVersion}.";

public static readonly string NewCommitMessage = Environment.GetEnvironmentVariable("DEVOPS_COMMIT_MSG") ?? "Automatic reference update";

public static readonly string AppVersion = "0.3.0";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public static async Task<WebhookRequestEntity> GetRequestData(this Stream body)
return JsonConvert.DeserializeObject<WebhookRequestEntity>(requestBody);
}

public static async Task<String> GetContent(this Stream item)
public static async Task<string> GetContent(this Stream item)
{
using (var ms = new MemoryStream())
{
Expand All @@ -34,7 +34,22 @@ public static async Task<String> GetContent(this Stream item)
}
}

public static Boolean StartsWith(this Byte[] content, Byte[] values, Int32 offset) =>
public static bool StartsWith(this byte[] content, byte[] values, int offset) =>
content.Length > offset && values.Any(m => m == content[offset]);

public static string ReplaceFromStartToQuote(this string content, string start, string replacement)
{
var index = content.IndexOf(start);

if (index != -1)
{
var end = index + start.Length;
var head = content.Substring(0, end);
var tail = content.Substring(content.IndexOf('"', end));
return $"{head}{replacement}{tail}";
}

return content;
}
}
}
12 changes: 12 additions & 0 deletions DevOpsLittleHelper/Common/HandlerOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Microsoft.Azure.WebJobs.Host;

namespace DevOpsLittleHelper
{
struct HandlerOptions
{
public TraceWriter Log;
public string AccessToken;
public string PackageName;
public string ProjectId;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Microsoft.Azure.WebJobs.Host;
using System;

namespace DevOpsLittleHelper
{
Expand All @@ -12,6 +11,6 @@ public HelperBase(TraceWriter log)
_log = log;
}

protected void Log(String message) => _log.Info(message);
protected void Log(string message) => _log.Info(message);
}
}
15 changes: 15 additions & 0 deletions DevOpsLittleHelper/Common/IPackageHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Threading.Tasks;

namespace DevOpsLittleHelper
{
internal interface IPackageHandler
{
string Name { get; }

Task<string> GetVersion();

Task<bool> ShouldUpdate(string path);

Task<string> Update(string content, string version);
}
}
7 changes: 7 additions & 0 deletions DevOpsLittleHelper/Common/ITemplateReplacer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace DevOpsLittleHelper
{
internal interface ITemplateReplacer
{
string MakeString(string template);
}
}
24 changes: 24 additions & 0 deletions DevOpsLittleHelper/Common/PackageHandlerFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;

namespace DevOpsLittleHelper
{
internal static class PackageHandlerFactory
{
private static readonly Dictionary<string, Func<HandlerOptions, IPackageHandler>> _handlers = new Dictionary<string, Func<HandlerOptions, IPackageHandler>>(StringComparer.OrdinalIgnoreCase)
{
{ "dotnet", options => new DotnetPackageHandler(options) },
{ "nodejs", options => new NodejsPackageHandler(options) },
};

public static IPackageHandler Create(string packageType, HandlerOptions options)
{
if (_handlers.TryGetValue(packageType, out var creator))
{
return creator.Invoke(options);
}

throw new NotSupportedException("The provided package type is not supported");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,70 +13,75 @@ internal class RepositoryHelper : HelperBase
{
private static readonly Uri collectionUri = new Uri(Constants.VsoApiRoot);

private readonly String _projectId;
private readonly string _projectId;
private readonly GitHttpClient _gitClient;

public RepositoryHelper(String projectId, String pat, TraceWriter log)
public RepositoryHelper(string projectId, string pat, TraceWriter log)
: base(log)
{
_projectId = projectId;
_gitClient = CreateGitClient(pat);
}

public async Task<List<Int32>> UpdateReferencesAndCreatePullRequests(String packageName, String packageVersion)
public async Task<List<int>> UpdateReferencesAndCreatePullRequests(IPackageHandler handler, string packageVersion)
{
var results = new List<Int32>();
var results = new List<int>();
var allRepositories = await _gitClient.GetRepositoriesAsync(_projectId).ConfigureAwait(false);
Log($"Received repository list: {String.Join(", ", allRepositories.Select(m => m.Name))}.");
Log($"Received repository list: {string.Join(", ", allRepositories.Select(m => m.Name))}.");

foreach (var repo in allRepositories)
{
var branchName = GetBranchName(repo.DefaultBranch);
var pr = await UpdateReferencesAndCreatePullRequest(repo.Name, branchName, packageName, packageVersion).ConfigureAwait(false);

if (pr.HasValue)
// New repositories have no branches yet -> no default branch
if (repo.DefaultBranch != null)
{
results.Add(pr.Value);
var branchName = GetBranchName(repo.DefaultBranch);
var pr = await UpdateReferencesAndCreatePullRequest(repo.Name, branchName, handler, packageVersion).ConfigureAwait(false);

if (pr.HasValue)
{
results.Add(pr.Value);
}
}
}

return results;
}

public async Task<Int32?> UpdateReferencesAndCreatePullRequest(String repoName, String baseBranchName, String packageName, String packageVersion)
public async Task<int?> UpdateReferencesAndCreatePullRequest(string repoName, string baseBranchName, IPackageHandler handler, string packageVersion)
{
var replacer = new TemplateReplacer(handler.Name, packageVersion);
var repo = await _gitClient.GetRepositoryAsync(_projectId, repoName).ConfigureAwait(false);
Log($"Received info about repo {repoName}.");

var branches = await _gitClient.GetBranchesAsync(repo.Id).ConfigureAwait(false);
var targetBranchName = GetTargetBranchName(packageName, packageVersion);
var targetBranchName = GetTargetBranchName(replacer);

var branch =
branches.FirstOrDefault(m => String.Equals(m.Name, targetBranchName, StringComparison.InvariantCultureIgnoreCase)) ??
branches.FirstOrDefault(m => String.Equals(m.Name, baseBranchName, StringComparison.InvariantCultureIgnoreCase)) ??
branches.FirstOrDefault(m => string.Equals(m.Name, targetBranchName, StringComparison.InvariantCultureIgnoreCase)) ??
branches.FirstOrDefault(m => string.Equals(m.Name, baseBranchName, StringComparison.InvariantCultureIgnoreCase)) ??
branches.First();

var lastCommit = branch.Commit.CommitId;
var versionRef = GetVersionRef(branch);
Log($"Received info about branches ({String.Join(", ", branches.Select(m => m.Name))}). Selected '{branch.Name}'.");
Log($"Received info about branches ({string.Join(", ", branches.Select(m => m.Name))}). Selected '{branch.Name}'.");

var items = await _gitClient.GetItemsAsync(_projectId, repo.Id, versionDescriptor: versionRef, recursionLevel: VersionControlRecursionType.Full).ConfigureAwait(false);
var changes = await GetChanges(repo.Id, packageName, packageVersion, versionRef, items).ConfigureAwait(false);
return await CreatePullRequestIfChanged(repo.Id, changes, lastCommit, branch.Name, targetBranchName, packageName, packageVersion).ConfigureAwait(false);
var changes = await GetChanges(repo.Id, handler, packageVersion, versionRef, items).ConfigureAwait(false);
return await CreatePullRequestIfChanged(repo.Id, changes, lastCommit, branch.Name, targetBranchName, replacer).ConfigureAwait(false);
}

private async Task<Int32?> CreatePullRequestIfChanged(Guid repoId, List<GitChange> changes, String lastCommit, String baseBranchName, String targetBranchName, String packageName, String packageVersion)
private async Task<int?> CreatePullRequestIfChanged(Guid repoId, List<GitChange> changes, string lastCommit, string baseBranchName, string targetBranchName, ITemplateReplacer replacer)
{
if (changes.Count > 0)
{
var title = GetTitle(packageName, packageVersion);
var description = GetDescription(packageName, packageVersion);
var commitMessage = GetCommitMessage(packageName, packageVersion);
var title = GetTitle(replacer);
var description = GetDescription(replacer);
var commitMessage = GetCommitMessage(replacer);
var push = CreatePush(lastCommit, changes, targetBranchName, commitMessage);
await _gitClient.CreatePushAsync(push, repoId).ConfigureAwait(false);
Log($"Push for {repoId} at {targetBranchName} created.");

if (!String.Equals(baseBranchName, targetBranchName, StringComparison.InvariantCultureIgnoreCase))
if (!string.Equals(baseBranchName, targetBranchName, StringComparison.InvariantCultureIgnoreCase))
{
var pr = CreatePullRequest(baseBranchName, targetBranchName, title, description);
var result = await _gitClient.CreatePullRequestAsync(pr, repoId).ConfigureAwait(false);
Expand All @@ -90,19 +95,21 @@ public async Task<List<Int32>> UpdateReferencesAndCreatePullRequests(String pack
return null;
}

private async Task<List<GitChange>> GetChanges(Guid repoId, String packageName, String packageVersion, GitVersionDescriptor versionRef, IEnumerable<GitItem> items)
private async Task<List<GitChange>> GetChanges(Guid repoId, IPackageHandler handler, string packageVersion, GitVersionDescriptor versionRef, IEnumerable<GitItem> items)
{
var changes = new List<GitChange>();

foreach (var item in items)
{
if (item.Path.EndsWith(".csproj"))
var shouldUpdate = await handler.ShouldUpdate(item.Path).ConfigureAwait(false);

if (shouldUpdate)
{
var itemRef = await _gitClient.GetItemContentAsync(repoId, item.Path, includeContent: true, versionDescriptor: versionRef).ConfigureAwait(false);
var oldContent = await itemRef.GetContent().ConfigureAwait(false);
var newContent = ReplaceInContent(oldContent, packageName, packageVersion);
var newContent = await handler.Update(oldContent, packageVersion).ConfigureAwait(false);

if (!String.Equals(oldContent, newContent))
if (!string.Equals(oldContent, newContent))
{
changes.Add(CreateChange(item.Path, newContent));
Log($"Item content of {item.Path} received and changed.");
Expand All @@ -113,22 +120,6 @@ private async Task<List<GitChange>> GetChanges(Guid repoId, String packageName,
return changes;
}

private static String ReplaceInContent(String oldContent, String packageName, String packageVersion)
{
var start = $"<PackageReference Include=\"{packageName}\" Version=\"";
var index = oldContent.IndexOf(start);

if (index != -1)
{
var end = index + start.Length;
var head = oldContent.Substring(0, end);
var tail = oldContent.Substring(oldContent.IndexOf('"', end));
return $"{head}{packageVersion}{tail}";
}

return oldContent;
}


private static GitVersionDescriptor GetVersionRef(GitBranchStats branch) => new GitVersionDescriptor
{
Expand All @@ -141,15 +132,15 @@ private static String ReplaceInContent(String oldContent, String packageName, St
ItemVersion = itemVersion,
};

private static GitPullRequest CreatePullRequest(String baseBranchName, String targetBranchName, String title, String description) => new GitPullRequest
private static GitPullRequest CreatePullRequest(string baseBranchName, string targetBranchName, string title, string description) => new GitPullRequest
{
Title = title,
Description = description,
TargetRefName = GetRefName(baseBranchName),
SourceRefName = GetRefName(targetBranchName),
};

private static GitChange CreateChange(String path, String content) => new GitChange
private static GitChange CreateChange(string path, string content) => new GitChange
{
ChangeType = VersionControlChangeType.Edit,
Item = new GitItem
Expand All @@ -163,7 +154,7 @@ private static String ReplaceInContent(String oldContent, String packageName, St
},
};

private static GitPush CreatePush(String commitId, IEnumerable<GitChange> changes, String branchName, String commitMessage) => new GitPush
private static GitPush CreatePush(string commitId, IEnumerable<GitChange> changes, string branchName, string commitMessage) => new GitPush
{
RefUpdates = new List<GitRefUpdate>
{
Expand All @@ -183,31 +174,25 @@ private static String ReplaceInContent(String oldContent, String packageName, St
},
};

private static String GetCommitMessage(String packageName, String packageVersion) =>
MakeString(Constants.NewCommitMessage, packageName, packageVersion);

private static String GetDescription(String packageName, String packageVersion) =>
MakeString(Constants.NewPrDescription, packageName, packageVersion);
private static string GetCommitMessage(ITemplateReplacer replacer) =>
replacer.MakeString(Constants.NewCommitMessage);

private static String GetTitle(String packageName, String packageVersion) =>
MakeString(Constants.NewPrTitle, packageName, packageVersion);
private static string GetDescription(ITemplateReplacer replacer) =>
replacer.MakeString(Constants.NewPrDescription);

private static String GetTargetBranchName(String packageName, String packageVersion) =>
MakeString(Constants.NewBranchName, packageName, packageVersion);
private static string GetTitle(ITemplateReplacer replacer) =>
replacer.MakeString(Constants.NewPrTitle);

private static String MakeString(String template, String packageName, String packageVersion) => template
.Replace("{packageName}", packageName)
.Replace("{packageVersion}", packageVersion)
.Replace("{appVersion}", Constants.AppVersion)
.Replace("{suffix}", $"{packageName}-{packageVersion}".Replace('.', '-').ToLower());
private static string GetTargetBranchName(ITemplateReplacer replacer) =>
replacer.MakeString(Constants.NewBranchName);

private static String GetRefName(String branchName) => $"refs/heads/{branchName}";
private static string GetRefName(string branchName) => $"refs/heads/{branchName}";

private static String GetBranchName(String refName) => refName.Replace("refs/heads/", String.Empty);
private static string GetBranchName(string refName) => refName.Replace("refs/heads/", string.Empty);

private static GitHttpClient CreateGitClient(String pat)
private static GitHttpClient CreateGitClient(string pat)
{
var creds = new VssBasicCredential(String.Empty, pat);
var creds = new VssBasicCredential(string.Empty, pat);

// Connect to Azure DevOps Services
var connection = new VssConnection(collectionUri, creds);
Expand Down
20 changes: 20 additions & 0 deletions DevOpsLittleHelper/Common/TemplateReplacer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace DevOpsLittleHelper
{
internal class TemplateReplacer : ITemplateReplacer
{
private readonly string _packageName;
private readonly string _packageVersion;

public TemplateReplacer(string packageName, string packageVersion)
{
_packageName = packageName;
_packageVersion = packageVersion;
}

public string MakeString(string template) => template
.Replace("{packageName}", _packageName)
.Replace("{packageVersion}", _packageVersion)
.Replace("{appVersion}", Constants.AppVersion)
.Replace("{suffix}", $"{_packageName}-{_packageVersion}".Replace('.', '-').ToLower());
}
}
Loading

0 comments on commit c128ee9

Please sign in to comment.