Skip to content

Commit c1673e9

Browse files
lbussellmthalman
andauthored
Move GitHub authentication options to a separate class (#1660)
Co-authored-by: Matt Thalman <[email protected]>
1 parent 92b97b5 commit c1673e9

19 files changed

+163
-80
lines changed

Diff for: Microsoft.DotNet.DockerTools.sln

+18
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A35A8A12-AC5
1919
EndProject
2020
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.DotNet.ImageBuilder", "src\Microsoft.DotNet.ImageBuilder\src\Microsoft.DotNet.ImageBuilder.csproj", "{25B808D8-AF2F-4C51-BBF2-E2BE83C97E20}"
2121
EndProject
22+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{F124FA66-7255-8556-598B-1D8A12763BBD}"
23+
EndProject
24+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.DotNet.ImageBuilder.Tests", "src\Microsoft.DotNet.ImageBuilder\tests\Microsoft.DotNet.ImageBuilder.Tests.csproj", "{66621167-DB12-484B-A5E8-E02FB7F84F2F}"
25+
EndProject
2226
Global
2327
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2428
Debug|Any CPU = Debug|Any CPU
@@ -65,6 +69,18 @@ Global
6569
{25B808D8-AF2F-4C51-BBF2-E2BE83C97E20}.Release|x64.Build.0 = Release|Any CPU
6670
{25B808D8-AF2F-4C51-BBF2-E2BE83C97E20}.Release|x86.ActiveCfg = Release|Any CPU
6771
{25B808D8-AF2F-4C51-BBF2-E2BE83C97E20}.Release|x86.Build.0 = Release|Any CPU
72+
{66621167-DB12-484B-A5E8-E02FB7F84F2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
73+
{66621167-DB12-484B-A5E8-E02FB7F84F2F}.Debug|Any CPU.Build.0 = Debug|Any CPU
74+
{66621167-DB12-484B-A5E8-E02FB7F84F2F}.Debug|x64.ActiveCfg = Debug|Any CPU
75+
{66621167-DB12-484B-A5E8-E02FB7F84F2F}.Debug|x64.Build.0 = Debug|Any CPU
76+
{66621167-DB12-484B-A5E8-E02FB7F84F2F}.Debug|x86.ActiveCfg = Debug|Any CPU
77+
{66621167-DB12-484B-A5E8-E02FB7F84F2F}.Debug|x86.Build.0 = Debug|Any CPU
78+
{66621167-DB12-484B-A5E8-E02FB7F84F2F}.Release|Any CPU.ActiveCfg = Release|Any CPU
79+
{66621167-DB12-484B-A5E8-E02FB7F84F2F}.Release|Any CPU.Build.0 = Release|Any CPU
80+
{66621167-DB12-484B-A5E8-E02FB7F84F2F}.Release|x64.ActiveCfg = Release|Any CPU
81+
{66621167-DB12-484B-A5E8-E02FB7F84F2F}.Release|x64.Build.0 = Release|Any CPU
82+
{66621167-DB12-484B-A5E8-E02FB7F84F2F}.Release|x86.ActiveCfg = Release|Any CPU
83+
{66621167-DB12-484B-A5E8-E02FB7F84F2F}.Release|x86.Build.0 = Release|Any CPU
6884
EndGlobalSection
6985
GlobalSection(SolutionProperties) = preSolution
7086
HideSolutionNode = FALSE
@@ -76,5 +92,7 @@ Global
7692
{5642BE26-182A-8523-32C6-646C04E8E66B} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
7793
{A35A8A12-AC52-E606-2330-8FAB7C79392D} = {5642BE26-182A-8523-32C6-646C04E8E66B}
7894
{25B808D8-AF2F-4C51-BBF2-E2BE83C97E20} = {A35A8A12-AC52-E606-2330-8FAB7C79392D}
95+
{F124FA66-7255-8556-598B-1D8A12763BBD} = {5642BE26-182A-8523-32C6-646C04E8E66B}
96+
{66621167-DB12-484B-A5E8-E02FB7F84F2F} = {F124FA66-7255-8556-598B-1D8A12763BBD}
7997
EndGlobalSection
8098
EndGlobal

Diff for: src/Microsoft.DotNet.ImageBuilder/src/Commands/GetStaleImagesCommand.cs

+2-5
Original file line numberDiff line numberDiff line change
@@ -158,17 +158,14 @@ private async Task<List<string>> GetPathsToRebuildAsync(
158158

159159
private async Task<ImageArtifactDetails> GetImageInfoForSubscriptionAsync(Models.Subscription.Subscription subscription, ManifestInfo manifest)
160160
{
161-
IApiConnection connection = OctokitClientFactory.CreateApiConnection(Options.GitOptions.ToOctokitCredentials());
162-
163-
ITreesClient treesClient = _octokitClientFactory.CreateTreesClient(connection);
161+
ITreesClient treesClient = _octokitClientFactory.CreateTreesClient(Options.GitOptions.GitHubAuthOptions);
164162
string fileSha = await treesClient.GetFileShaAsync(
165163
subscription.ImageInfo.Owner, subscription.ImageInfo.Repo, subscription.ImageInfo.Branch, subscription.ImageInfo.Path);
166164

167-
IBlobsClient blobsClient = _octokitClientFactory.CreateBlobsClient(connection);
165+
IBlobsClient blobsClient = _octokitClientFactory.CreateBlobsClient(Options.GitOptions.GitHubAuthOptions);
168166
string imageDataJson = await blobsClient.GetFileContentAsync(subscription.ImageInfo.Owner, subscription.ImageInfo.Repo, fileSha);
169167

170168
return ImageInfoHelper.LoadFromContent(imageDataJson, manifest, skipManifestValidation: true);
171169
}
172170
}
173171
}
174-
#nullable disable

Diff for: src/Microsoft.DotNet.ImageBuilder/src/Commands/GitOptions.cs

+52-17
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System;
66
using System.Collections.Generic;
77
using System.CommandLine;
8+
using System.Linq;
89
using Microsoft.DotNet.VersionTools.Automation;
910
using Octokit;
1011
using static Microsoft.DotNet.ImageBuilder.Commands.CliHelper;
@@ -14,22 +15,23 @@ namespace Microsoft.DotNet.ImageBuilder.Commands
1415
{
1516
public class GitOptions : IGitHubFileRef
1617
{
17-
public string AuthToken { get; set; } = string.Empty;
1818
public string Branch { get; set; } = string.Empty;
1919
public string Email { get; set; } = string.Empty;
2020
public string Owner { get; set; } = string.Empty;
2121
public string Path { get; set; } = string.Empty;
2222
public string Repo { get; set; } = string.Empty;
2323
public string Username { get; set; } = string.Empty;
2424

25-
public GitHubAuth ToGitHubAuth()
26-
{
27-
return new GitHubAuth(AuthToken, Username, Email);
28-
}
25+
public Uri GetRepoUrl() => new Uri($"https://github.com/{Owner}/{Repo}");
26+
27+
public GitHubAuthOptions GitHubAuthOptions { get; set; } = new GitHubAuthOptions();
28+
}
2929

30-
public Credentials ToOctokitCredentials() => new Credentials(AuthToken);
30+
public record GitHubAuthOptions(string AuthToken = "", string PrivateKeyFilePath = "")
31+
{
32+
public bool IsPrivateKeyAuth => !string.IsNullOrEmpty(PrivateKeyFilePath);
3133

32-
public Uri GetRepoUrl() => new Uri($"https://github.com/{Owner}/{Repo}");
34+
public bool HasCredentials => !string.IsNullOrEmpty(AuthToken) || !string.IsNullOrEmpty(PrivateKeyFilePath);
3335
}
3436

3537
public class GitOptionsBuilder
@@ -47,7 +49,7 @@ public static GitOptionsBuilder BuildWithDefaults() =>
4749
Build()
4850
.WithUsername(isRequired: true)
4951
.WithEmail(isRequired: true)
50-
.WithAuthToken(isRequired: true)
52+
.WithGitHubAuth()
5153
.WithBranch()
5254
.WithOwner()
5355
.WithPath()
@@ -65,12 +67,6 @@ public GitOptionsBuilder WithEmail(
6567
string description = "Email to use for GitHub connection") =>
6668
AddSymbol("git-email", nameof(GitOptions.Email), isRequired, defaultValue, description);
6769

68-
public GitOptionsBuilder WithAuthToken(
69-
bool isRequired = false,
70-
string? defaultValue = null,
71-
string description = "Auth token to use to connect to GitHub") =>
72-
AddSymbol("git-token", nameof(GitOptions.AuthToken), isRequired, defaultValue, description);
73-
7470
public GitOptionsBuilder WithBranch(
7571
bool isRequired = false,
7672
string? defaultValue = null,
@@ -95,23 +91,62 @@ public GitOptionsBuilder WithRepo(
9591
string description = "Name of the GitHub repo to access") =>
9692
AddSymbol("git-repo", nameof(GitOptions.Repo), isRequired, defaultValue, description);
9793

94+
public GitOptionsBuilder WithGitHubAuth(string? description = null, bool isRequired = false)
95+
{
96+
description ??= "GitHub Personal Access Token (PAT) or private key file (.pem)";
97+
description += " [token=<token> | private-key-file=<path to .pem file>]";
98+
99+
_options.Add(CreateOption(
100+
"github-auth",
101+
nameof(GitOptions.GitHubAuthOptions),
102+
"GitHub Personal Access Token (PAT) or private key file (.pem) [token=<token> | private-key-file=<path to .pem file>]",
103+
parseArg: argumentResult =>
104+
{
105+
var dictionary = argumentResult.Tokens
106+
.Select(token => token.Value.ParseKeyValuePair('='))
107+
.ToDictionary();
108+
109+
string token = dictionary.GetValueOrDefault("token", "");
110+
string privateKeyFile = dictionary.GetValueOrDefault("private-key-file", "");
111+
112+
if (isRequired && string.IsNullOrEmpty(token) && string.IsNullOrEmpty(privateKeyFile))
113+
{
114+
throw new ArgumentException("GitHub token or private key file must be provided.");
115+
}
116+
117+
return new GitHubAuthOptions(token, privateKeyFile);
118+
}
119+
));
120+
121+
return this;
122+
}
123+
98124
public IEnumerable<Option> GetCliOptions() => _options;
99125

100126
public IEnumerable<Argument> GetCliArguments() => _arguments;
101127

102-
private GitOptionsBuilder AddSymbol<T>(string alias, string propertyName, bool isRequired, T? defaultValue, string description)
128+
private GitOptionsBuilder AddSymbol<T>(
129+
string alias,
130+
string propertyName,
131+
bool isRequired,
132+
T? defaultValue,
133+
string description)
103134
{
104135
if (isRequired)
105136
{
106137
_arguments.Add(new Argument<T>(propertyName, description));
107138
}
108139
else
109140
{
110-
_options.Add(CreateOption<T>(alias, propertyName, description, defaultValue is null ? default! : defaultValue));
141+
_options.Add(
142+
CreateOption(
143+
alias,
144+
propertyName,
145+
description,
146+
defaultValue is null ? default! : defaultValue));
111147
}
112148

113149
return this;
114150
}
115151
}
116152
}
117-
#nullable disable

Diff for: src/Microsoft.DotNet.ImageBuilder/src/Commands/PostPublishNotificationCommand.cs

+6-5
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,17 @@ public class PostPublishNotificationCommand : ManifestCommand<PostPublishNotific
2626
{
2727
private readonly IVssConnectionFactory _connectionFactory;
2828
private readonly INotificationService _notificationService;
29+
private readonly IOctokitClientFactory _octokitClientFactory;
2930

3031
[ImportingConstructor]
3132
public PostPublishNotificationCommand(
3233
IVssConnectionFactory connectionFactory,
33-
INotificationService notificationService)
34+
INotificationService notificationService,
35+
IOctokitClientFactory octokitClientFactory)
3436
{
3537
_connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
3638
_notificationService = notificationService ?? throw new ArgumentNullException(nameof(notificationService));
39+
_octokitClientFactory = octokitClientFactory ?? throw new ArgumentNullException(nameof(octokitClientFactory));
3740
}
3841

3942
protected override string Description => "Posts a notification about a publishing event";
@@ -107,7 +110,7 @@ await _notificationService.PostAsync(
107110
}.AppendIf(NotificationLabels.Failure, () => overallResult == BuildResult.Failed),
108111
Options.GitOptions.Owner,
109112
Options.GitOptions.Repo,
110-
Options.GitOptions.AuthToken,
113+
Options.GitOptions.GitHubAuthOptions,
111114
Options.IsDryRun,
112115
imagesMarkdown);
113116
}
@@ -117,8 +120,7 @@ await _notificationService.PostAsync(
117120
// In the case where the publish build was queued by AutoBuilder, this finds the GitHub issue associated
118121
// with that queued build.
119122

120-
GitHubClient gitHubClient = new(new ProductHeaderValue("dotnet"));
121-
Credentials token = new(Options.GitOptions.AuthToken);
123+
IGitHubClient gitHubClient = _octokitClientFactory.CreateGitHubClient(Options.GitOptions.GitHubAuthOptions);
122124
RepositoryIssueRequest issueRequest = new()
123125
{
124126
Filter = IssueFilter.All,
@@ -127,7 +129,6 @@ await _notificationService.PostAsync(
127129
issueRequest.Labels.Add(NotificationLabels.AutoBuilder);
128130
issueRequest.Labels.Add(NotificationLabels.GetRepoLocationLabel(Options.SourceRepo, Options.SourceBranch));
129131

130-
gitHubClient.Credentials = token;
131132
IReadOnlyList<Octokit.Issue> issues = await gitHubClient.Issue.GetAllForRepository(
132133
Options.GitOptions.Owner, Options.GitOptions.Repo, issueRequest);
133134

Diff for: src/Microsoft.DotNet.ImageBuilder/src/Commands/PostPublishNotificationOptions.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ public class PostPublishNotificationOptionsBuilder : ManifestOptionsBuilder
2525
private readonly AzdoOptionsBuilder _azdoOptionsBuilder = new();
2626
private readonly GitOptionsBuilder _gitOptionsBuilder =
2727
GitOptionsBuilder.Build()
28-
.WithAuthToken(isRequired: true, description: "Auth token to use to connect to GitHub for posting notifications")
28+
.WithGitHubAuth(
29+
isRequired: true,
30+
description: "Auth token to use to connect to GitHub for posting notifications")
2931
.WithOwner(isRequired: true, description: "Owner of the GitHub repo to post notifications to")
3032
.WithRepo(isRequired: true, description: "Name of the GitHub repo to post notifications to");
3133

Diff for: src/Microsoft.DotNet.ImageBuilder/src/Commands/PublishImageInfoCommand.cs

+10-10
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.IO;
88
using System.Threading.Tasks;
99
using LibGit2Sharp;
10+
using LibGit2Sharp.Handlers;
1011

1112
#nullable enable
1213
namespace Microsoft.DotNet.ImageBuilder.Commands
@@ -42,11 +43,7 @@ public override Task ExecuteAsync()
4243
_loggerService.WriteSubheading("Cloning GitHub repo");
4344

4445
CloneOptions cloneOptions = new() { BranchName = Options.GitOptions.Branch };
45-
cloneOptions.FetchOptions.CredentialsProvider = (url, user, cred) => new UsernamePasswordCredentials
46-
{
47-
Username = Options.GitOptions.AuthToken,
48-
Password = string.Empty
49-
};
46+
cloneOptions.FetchOptions.CredentialsProvider = GetCredentials(Options.GitOptions.GitHubAuthOptions);
5047

5148
using IRepository repo =_gitService.CloneRepository(
5249
$"https://github.com/{Options.GitOptions.Owner}/{Options.GitOptions.Repo}",
@@ -96,15 +93,18 @@ private void UpdateGitRepos(string repoPath, IRepository repo)
9693
repo.Network.Push(branch,
9794
new PushOptions
9895
{
99-
CredentialsProvider = (url, user, cred) => new UsernamePasswordCredentials
100-
{
101-
Username = Options.GitOptions.AuthToken,
102-
Password = string.Empty
103-
}
96+
CredentialsProvider = GetCredentials(Options.GitOptions.GitHubAuthOptions)
10497
});
10598

10699
Uri gitHubCommitUrl = GitHelper.GetCommitUrl(Options.GitOptions, commit.Sha);
107100
_loggerService.WriteMessage($"The '{Options.GitOptions.Path}' file was updated: {gitHubCommitUrl}");
108101
}
102+
103+
private static CredentialsHandler GetCredentials(GitHubAuthOptions gitHubAuthOptions) =>
104+
(url, user, cred) => new UsernamePasswordCredentials
105+
{
106+
Username = gitHubAuthOptions.AuthToken,
107+
Password = string.Empty
108+
};
109109
}
110110
}

Diff for: src/Microsoft.DotNet.ImageBuilder/src/Commands/PublishMcrDocsCommand.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public override async Task ExecuteAsync()
5959

6060
if (!Options.IsDryRun)
6161
{
62-
using IGitHubClient gitHubClient = _gitHubClientFactory.GetClient(Options.GitOptions.ToGitHubAuth(), Options.IsDryRun);
62+
using IGitHubClient gitHubClient = _gitHubClientFactory.GetClient(Options.GitOptions, Options.IsDryRun);
6363

6464
await RetryHelper.GetWaitAndRetryPolicy<HttpRequestException>(_loggerService).ExecuteAsync(async () =>
6565
{

Diff for: src/Microsoft.DotNet.ImageBuilder/src/Commands/QueueBuildCommand.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ private async Task LogAndNotifyResultsAsync(
249249
notificationMarkdown.AppendLine();
250250
notificationMarkdown.AppendLine(NotificationHelper.FormatNotificationMetadata(queueInfo));
251251

252-
if (Options.GitOptions.AuthToken == string.Empty ||
252+
if (!Options.GitOptions.GitHubAuthOptions.HasCredentials ||
253253
Options.GitOptions.Owner == string.Empty ||
254254
Options.GitOptions.Repo == string.Empty)
255255
{
@@ -267,7 +267,7 @@ await _notificationService.PostAsync(
267267
}.AppendIf(NotificationLabels.Failure, () => exception is not null),
268268
Options.GitOptions.Owner,
269269
Options.GitOptions.Repo,
270-
Options.GitOptions.AuthToken,
270+
Options.GitOptions.GitHubAuthOptions,
271271
Options.IsDryRun);
272272
}
273273
}

Diff for: src/Microsoft.DotNet.ImageBuilder/src/Commands/QueueBuildOptions.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public class QueueBuildOptionsBuilder : CliOptionsBuilder
2323
private readonly AzdoOptionsBuilder _azdoOptionsBuilder = new();
2424
private readonly GitOptionsBuilder _gitOptionsBuilder =
2525
GitOptionsBuilder.Build()
26-
.WithAuthToken(description: "Auth token to use to connect to GitHub for posting notifications")
26+
.WithGitHubAuth(description: "Auth token to use to connect to GitHub for posting notifications")
2727
.WithOwner(description: "Owner of the GitHub repo to post notifications to")
2828
.WithRepo(description: "Name of the GitHub repo to post notifications to");
2929

Diff for: src/Microsoft.DotNet.ImageBuilder/src/GitHubClientFactory.cs

+8-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System;
66
using System.ComponentModel.Composition;
77
using System.Threading.Tasks;
8+
using Microsoft.DotNet.ImageBuilder.Commands;
89
using Microsoft.DotNet.VersionTools.Automation;
910
using Microsoft.DotNet.VersionTools.Automation.GitHubApi;
1011

@@ -21,9 +22,14 @@ public GitHubClientFactory(ILoggerService loggerService)
2122
_loggerService = loggerService ?? throw new ArgumentNullException(nameof(loggerService));
2223
}
2324

24-
public IGitHubClient GetClient(GitHubAuth gitHubAuth, bool isDryRun)
25+
public IGitHubClient GetClient(GitOptions gitOptions, bool isDryRun)
2526
{
26-
return new GitHubClientWrapper(_loggerService, new GitHubClient(gitHubAuth), isDryRun);
27+
var auth = new GitHubAuth(
28+
authToken: gitOptions.GitHubAuthOptions.AuthToken,
29+
user: gitOptions.Username,
30+
email: gitOptions.Email);
31+
32+
return new GitHubClientWrapper(_loggerService, new GitHubClient(auth), isDryRun);
2733
}
2834

2935
// Wrapper class to ensure that no operations with side-effects are invoked when the dry-run option is enabled

Diff for: src/Microsoft.DotNet.ImageBuilder/src/IGitHubClientFactory.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5-
using Microsoft.DotNet.VersionTools.Automation;
5+
using Microsoft.DotNet.ImageBuilder.Commands;
66
using Microsoft.DotNet.VersionTools.Automation.GitHubApi;
77

88
namespace Microsoft.DotNet.ImageBuilder
99
{
1010
public interface IGitHubClientFactory
1111
{
12-
IGitHubClient GetClient(GitHubAuth gitHubAuth, bool isDryRun);
12+
IGitHubClient GetClient(GitOptions gitOptions, bool isDryRun);
1313
}
1414
}

Diff for: src/Microsoft.DotNet.ImageBuilder/src/INotificationService.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Collections.Generic;
55
using System.Threading.Tasks;
6+
using Microsoft.DotNet.ImageBuilder.Commands;
67

78
#nullable enable
89
namespace Microsoft.DotNet.ImageBuilder;
@@ -15,7 +16,7 @@ Task PostAsync(
1516
IEnumerable<string> labels,
1617
string repoOwner,
1718
string repoName,
18-
string gitHubAccessToken,
19+
GitHubAuthOptions gitHubAuth,
1920
bool isDryRun,
2021
IEnumerable<string>? comments = null);
2122
}

0 commit comments

Comments
 (0)