-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathPaketUpgrader.cs
262 lines (212 loc) · 9.94 KB
/
PaketUpgrader.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
using Octokit;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
namespace PaketUpgrader
{
public class PaketUpgrader
{
private GitHubClient client;
private List<string> supportedPaketVersions = new List<string> { "b98e000b232408fe0a21730e88f89755f0d7a68c" };
public PaketUpgrader(GitHubClient client)
{
this.client = client;
}
public async Task Run(Repository repository, bool submitPullRequest, bool debug = false)
{
var owner = repository.Owner.Login;
var name = repository.Name;
var result = await Validate(repository);
if (result == PaketUpgrade.PaketNotFound)
{
if (debug)
{
Console.WriteLine($"Ignoring {repository.FullName} as Paket wasn't found in the repository");
}
return;
}
if (result == PaketUpgrade.UpToDate)
{
Console.WriteLine($"{repository.FullName} appears to be up-to-date");
return;
}
await PerformUpgrade(owner, name, submitPullRequest);
}
private async Task<PaketUpgrade> Validate(Repository repository)
{
try
{
var contents = await client.Repository.Content.GetAllContentsByRef(repository.Id, ".paket", repository.DefaultBranch);
var executables = contents.Where(c => c.Path.EndsWith(".exe"));
foreach (var executable in executables)
{
if (executable.Path.EndsWith("paket.exe") && !supportedPaketVersions.Contains(executable.Sha))
{
return PaketUpgrade.UpgradeNeeded;
}
if (executable.Path.EndsWith("paket.bootstrapper.exe") && !supportedPaketVersions.Contains(executable.Sha))
{
return PaketUpgrade.UpgradeNeeded;
}
}
}
catch
{
return PaketUpgrade.PaketNotFound;
}
return PaketUpgrade.UpToDate;
}
async Task PerformUpgrade(string owner, string name, bool submitPullRequest)
{
var openPullRequest = await HasOpenPullRequest(owner, name);
if (openPullRequest != null)
{
Console.WriteLine($"{owner}/{name} has an open pull request #{openPullRequest.Number}");
return;
}
if (!submitPullRequest)
{
Console.WriteLine($"{owner}/{name} requires update, but ignored");
return;
}
var repository = await FindRepositoryToSubmitPullRequestFrom(owner, name);
if (repository == null)
{
Console.WriteLine($" - Couldn't find a repository to use for upgrading {owner}/{name}");
return;
}
var reference = await CreateNewReferenceWithPatch(repository);
if (reference == null)
{
Console.WriteLine($" - Unable to create a new reference in th erepository {repository.FullName}");
return;
}
var pullRequest = await CreatePullRequest(owner, name, repository, reference);
Console.WriteLine($" - {owner}/{name} now has PR {pullRequest.Number} opened");
}
private async Task<PullRequest> CreatePullRequest(string owner, string name, Repository repository, Reference reference)
{
var branch = reference.Ref.Replace("refs/heads/", "");
var headRef = repository.Fork ? $"{repository.Owner.Login}:{branch}" : branch;
var newPullRequest = new NewPullRequest("Update paket to address TLS deprecation", headRef, repository.DefaultBranch);
newPullRequest.Body = @":wave: GitHub disabled TLS 1.0 and TLS 1.1 on February 22nd, which affected Paket and needs to be updated to 5.142 or later.
You can read more about this on the [GitHub Engineering blog](https://githubengineering.com/crypto-removal-notice/).
The update to Paket is explained here: https://github.com/fsprojects/Paket/pull/3066
The work to update Paket in the wild is occurring here: https://github.com/fsprojects/Paket/issues/3068";
var pullRequest = await client.PullRequest.Create(owner, name, newPullRequest);
return pullRequest;
}
async Task<PullRequest> HasOpenPullRequest(string owner, string name)
{
var pullRequests = await client.PullRequest.GetAllForRepository(owner, name, new ApiOptions() { PageSize = 100 });
foreach (var pullRequest in pullRequests)
{
var files = await client.PullRequest.Files(owner, name, pullRequest.Number);
var updatesPaketToLatestVersion = files.FirstOrDefault(f =>
f.FileName == ".paket/paket.exe" && supportedPaketVersions.Contains(f.Sha)
|| f.FileName == ".paket/paket.bootstrapper.exe" && supportedPaketVersions.Contains(f.Sha));
if (updatesPaketToLatestVersion != null)
{
return pullRequest;
}
}
return null;
}
async Task<Repository> FindRepositoryToSubmitPullRequestFrom(string owner, string name)
{
var repository = await client.Repository.Get(owner, name);
if (repository.Permissions.Push)
{
return repository;
}
var request = new RepositoryRequest()
{
Type = RepositoryType.Owner
};
var ownedRepos = await client.Repository.GetAllForCurrent(request, new ApiOptions() { PageSize = 100 });
var forks = ownedRepos.Where(r => r.Fork);
var matchingNames = forks.Where(r => r.Name == name);
var foundFork = matchingNames.FirstOrDefault();
// TODO: foundFork.Parent is null, and that might be a problem down the track
if (foundFork != null)
{
return foundFork;
}
return await client.Repository.Forks.Create(owner, name, new NewRepositoryFork());
}
async Task<string> GetNewExecutableBase64()
{
var assembly = Assembly.GetExecutingAssembly();
var resourceNames = assembly.GetManifestResourceNames();
var resourceName = resourceNames[0];
using (var stream = assembly.GetManifestResourceStream(resourceName))
{
var memoryStream = new MemoryStream();
await stream.CopyToAsync(memoryStream);
var bytes = memoryStream.ToArray();
return Convert.ToBase64String(bytes);
}
}
async Task<Reference> CreateNewReferenceWithPatch(Repository repository)
{
// first, create a new blob in the repository which is the new file contents
var blob = new NewBlob()
{
Content = await GetNewExecutableBase64(),
Encoding = EncodingType.Base64
};
var newBlob = await client.Git.Blob.Create(repository.Id, blob);
// we create the new reference for the PR branch
var defaultRef = $"heads/{repository.DefaultBranch}";
var defaultBranch = await client.Git.Reference.Get(repository.Id, defaultRef);
var initialSha = defaultBranch.Object.Sha;
var newRef = $"heads/bootstrapper";
var newReference = await client.Git.Reference.Create(repository.Id, new NewReference(newRef, initialSha));
var currentTree = await client.Git.Tree.Get(repository.Id, initialSha);
// update the paket subdirectory to assign the new blob to whatever executable
var paketTreeNode = currentTree.Tree.FirstOrDefault(t => t.Path == ".paket");
var paketTree = await client.Git.Tree.Get(repository.Id, paketTreeNode.Sha);
var executables = paketTree.Tree.Where(t => t.Path.EndsWith(".exe"));
if (executables.Count() == 0)
{
Console.WriteLine($"TODO: oh gosh, we're not able to find executables in the .paket directory");
return null;
}
var executable = executables.ElementAt(0);
var newPaketTree = new NewTree
{
BaseTree = paketTree.Sha,
};
newPaketTree.Tree.Add(new NewTreeItem()
{
Mode = executable.Mode,
Sha = newBlob.Sha,
Path = executable.Path,
Type = executable.Type.Value
});
var updatedPaketTree = await client.Git.Tree.Create(repository.Id, newPaketTree);
// update the root tree to use this new .paket directory
var newRootTree = new NewTree
{
BaseTree = currentTree.Sha
};
newRootTree.Tree.Add(new NewTreeItem()
{
Mode = paketTreeNode.Mode,
Sha = updatedPaketTree.Sha,
Path = paketTreeNode.Path,
Type = paketTreeNode.Type.Value
});
var updatedRootTree = await client.Git.Tree.Create(repository.Id, newRootTree);
// create a new commit using the updated tree
var newCommit = new NewCommit($"Updated {executable.Path} to address TLS 1.0 and 1.1 deprecation", updatedRootTree.Sha, initialSha);
var commit = await client.Git.Commit.Create(repository.Id, newCommit);
// then update the bootstrapper ref to this new commit
var updatedReference = await client.Git.Reference.Update(repository.Id, newRef, new ReferenceUpdate(commit.Sha));
return updatedReference;
}
}
}