Skip to content

Commit 5a2f2ff

Browse files
Merge branch 'main' into 1180-fix-url-validation
2 parents 2ef9d49 + 6cb8e63 commit 5a2f2ff

File tree

22 files changed

+411
-49
lines changed

22 files changed

+411
-49
lines changed

.github/workflows/CI.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,15 @@ on:
1111
- cron: "0 7 * * *"
1212
workflow_dispatch:
1313

14+
permissions:
15+
contents: read
16+
1417
jobs:
1518
build:
19+
permissions:
20+
contents: read
21+
actions: write
22+
security-events: write
1623
strategy:
1724
fail-fast: false
1825
matrix:
@@ -85,6 +92,9 @@ jobs:
8592
if: matrix.runner-os == 'ubuntu-latest'
8693

8794
upload-event-file:
95+
permissions:
96+
contents: read
97+
actions: write
8898
runs-on: ubuntu-latest
8999
steps:
90100
# This is used by the subsequent publish-test-results.yaml
@@ -95,6 +105,9 @@ jobs:
95105
path: ${{ github.event_path }}
96106

97107
build-for-e2e-test:
108+
permissions:
109+
contents: read
110+
actions: write
98111
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.owner.login == 'github'
99112
strategy:
100113
fail-fast: false
@@ -140,6 +153,10 @@ jobs:
140153
dist/win-x64/gei-windows-amd64.exe
141154
142155
e2e-test:
156+
permissions:
157+
contents: read
158+
actions: write
159+
checks: write
143160
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.owner.login == 'github'
144161
needs: [build-for-e2e-test]
145162
strategy:
@@ -276,6 +293,8 @@ jobs:
276293
shell: pwsh
277294

278295
publish:
296+
permissions:
297+
contents: write
279298
runs-on: ubuntu-latest
280299
if: startsWith(github.ref, 'refs/tags/v')
281300
needs: [build, e2e-test]
@@ -321,10 +340,12 @@ jobs:
321340
./dist/ado2gh.*.win-x64.zip
322341
./dist/ado2gh.*.win-x86.zip
323342
./dist/ado2gh.*.linux-x64.tar.gz
343+
./dist/ado2gh.*.linux-arm64.tar.gz
324344
./dist/ado2gh.*.osx-x64.tar.gz
325345
./dist/win-x64/gei-windows-amd64.exe
326346
./dist/win-x86/gei-windows-386.exe
327347
./dist/linux-x64/gei-linux-amd64
348+
./dist/linux-arm64/gei-linux-arm64
328349
./dist/osx-x64/gei-darwin-amd64
329350
330351
- name: Create gh-ado2gh Release
@@ -337,6 +358,7 @@ jobs:
337358
./dist/win-x86/ado2gh-windows-386.exe
338359
./dist/win-x64/ado2gh-windows-amd64.exe
339360
./dist/linux-x64/ado2gh-linux-amd64
361+
./dist/linux-arm64/ado2gh-linux-arm64
340362
./dist/osx-x64/ado2gh-darwin-amd64
341363
342364
- name: Create gh-bbs2gh Release
@@ -349,6 +371,7 @@ jobs:
349371
./dist/win-x86/bbs2gh-windows-386.exe
350372
./dist/win-x64/bbs2gh-windows-amd64.exe
351373
./dist/linux-x64/bbs2gh-linux-amd64
374+
./dist/linux-arm64/bbs2gh-linux-arm64
352375
./dist/osx-x64/bbs2gh-darwin-amd64
353376
354377
- name: Archive Release Notes

.github/workflows/publish-test-results.yml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,9 @@ jobs:
3535
3636
- name: Extract PR Number
3737
run: |
38-
eventjson=`cat 'artifacts/Event File/event.json'`
39-
prnumber=`echo $(jq -r '.pull_request.number' <<< "$eventjson")`
40-
echo "PR_NUMBER=$(echo $prnumber | tr -cd '0-9')" >> $GITHUB_ENV
41-
38+
prnumber=$(jq -r '.pull_request.number' < 'artifacts/Event File/event.json')
39+
sanitized_prnumber=$(grep -E '^[0-9]+$' <<< "$prnumber")
40+
echo "PR_NUMBER=$sanitized_prnumber" >> "$GITHUB_ENV"
4241
- name: Publish Unit Test Results
4342
uses: EnricoMi/publish-unit-test-result-action@v2
4443
with:

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,24 @@ When the CLI is launched, it logs if a newer version of the CLI is available. Yo
9595

9696
When the CLI is launched, it logs a warning if there are any ongoing [GitHub incidents](https://www.githubstatus.com/) that might affect your use of the CLI. You can skip this check by setting the `GEI_SKIP_STATUS_CHECK` environment variable to `true`.
9797

98+
### Configuring multipart upload chunk size
99+
100+
Set the `GITHUB_OWNED_STORAGE_MULTIPART_MEBIBYTES` environment variable to change the archive upload part size. Provide the value in mebibytes (MiB); For example:
101+
102+
```powershell
103+
# Windows PowerShell
104+
$env:GITHUB_OWNED_STORAGE_MULTIPART_MEBIBYTES = "10"
105+
```
106+
107+
```bash
108+
# macOS/Linux
109+
export GITHUB_OWNED_STORAGE_MULTIPART_MEBIBYTES=10
110+
```
111+
112+
This sets the chunk size to 10 MiB (10,485,760 bytes). The minimum supported value is 5 MiB, and the default remains 100 MiB.
113+
114+
This might be needed to improve upload reliability in environments with proxies or very slow connections.
115+
98116
## Contributions
99117

100118
See [Contributing](CONTRIBUTING.md) for more info on how to get involved.

RELEASENOTES.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
1-
- Added validation to detect and return clear error messages when a URL is provided instead of a name for organization, repository, or enterprise arguments (e.g., `--github-org`, `--github-target-org`, `--source-repo`, `--github-target-enterprise`)
2-
1+
- Added support for linux-arm64 architecture for all CLI binaries (gei, ado2gh, bbs2gh), enabling users to run GEI on ARM-based systems including MacOS with Apple Silicon inside ARC runners
2+
- Fixed issue where alert migration commands (migrate-code-scanning-alerts, migrate-secret-alerts) required GH_PAT environment variable even when GitHub tokens were provided via command-line arguments (--github-source-pat, --github-target-pat). These commands now work correctly with CLI-only token authentication.
3+
- Added support for configurable multipart upload chunk size for GitHub-owned storage uploads via `GITHUB_OWNED_STORAGE_MULTIPART_MEBIBYTES` environment variable (minimum 5 MiB, default 100 MiB) to improve upload reliability in environments with proxies or slow connections
4+
- Added validation to detect and return clear error messages when a URL is provided instead of a name for organization, repository, or enterprise arguments (e.g., `--github-org`, `--github-target-org`, `--source-repo`, `--github-target-enterprise`)

publish.ps1

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,21 @@ else {
5959
}
6060

6161
Copy-Item ./dist/linux-x64/ado2gh ./dist/linux-x64/ado2gh-linux-amd64
62+
63+
# linux-arm64 build
64+
dotnet publish src/ado2gh/ado2gh.csproj -c Release -o dist/linux-arm64/ -r linux-arm64 -p:PublishSingleFile=true -p:PublishTrimmed=true -p:TrimMode=partial --self-contained true /p:DebugType=None /p:IncludeNativeLibrariesForSelfExtract=true /p:VersionPrefix=$AssemblyVersion
65+
66+
if ($LASTEXITCODE -ne 0) {
67+
exit $LASTEXITCODE
68+
}
69+
70+
tar -cvzf ./dist/ado2gh.$AssemblyVersion.linux-arm64.tar.gz -C ./dist/linux-arm64 ado2gh
71+
72+
if (Test-Path -Path ./dist/linux-arm64/ado2gh-linux-arm64) {
73+
Remove-Item ./dist/linux-arm64/ado2gh-linux-arm64
74+
}
75+
76+
Copy-Item ./dist/linux-arm64/ado2gh ./dist/linux-arm64/ado2gh-linux-arm64
6277
}
6378

6479
if ((Test-Path env:SKIP_MACOS) -And $env:SKIP_MACOS.ToUpper() -eq "TRUE") {
@@ -126,6 +141,19 @@ else {
126141
}
127142

128143
Rename-Item ./dist/linux-x64/gei gei-linux-amd64
144+
145+
# linux-arm64 build
146+
dotnet publish src/gei/gei.csproj -c Release -o dist/linux-arm64/ -r linux-arm64 -p:PublishSingleFile=true -p:PublishTrimmed=true -p:TrimMode=partial --self-contained true /p:DebugType=None /p:IncludeNativeLibrariesForSelfExtract=true /p:VersionPrefix=$AssemblyVersion
147+
148+
if ($LASTEXITCODE -ne 0) {
149+
exit $LASTEXITCODE
150+
}
151+
152+
if (Test-Path -Path ./dist/linux-arm64/gei-linux-arm64) {
153+
Remove-Item ./dist/linux-arm64/gei-linux-arm64
154+
}
155+
156+
Rename-Item ./dist/linux-arm64/gei gei-linux-arm64
129157
}
130158

131159
if ((Test-Path env:SKIP_MACOS) -And $env:SKIP_MACOS.ToUpper() -eq "TRUE") {
@@ -190,6 +218,19 @@ else {
190218
}
191219

192220
Rename-Item ./dist/linux-x64/bbs2gh bbs2gh-linux-amd64
221+
222+
# linux-arm64 build
223+
dotnet publish src/bbs2gh/bbs2gh.csproj -c Release -o dist/linux-arm64/ -r linux-arm64 -p:PublishSingleFile=true -p:PublishTrimmed=true -p:TrimMode=partial --self-contained true /p:DebugType=None /p:IncludeNativeLibrariesForSelfExtract=true /p:VersionPrefix=$AssemblyVersion
224+
225+
if ($LASTEXITCODE -ne 0) {
226+
exit $LASTEXITCODE
227+
}
228+
229+
if (Test-Path -Path ./dist/linux-arm64/bbs2gh-linux-arm64) {
230+
Remove-Item ./dist/linux-arm64/bbs2gh-linux-arm64
231+
}
232+
233+
Rename-Item ./dist/linux-arm64/bbs2gh bbs2gh-linux-arm64
193234
}
194235

195236
if ((Test-Path env:SKIP_MACOS) -And $env:SKIP_MACOS.ToUpper() -eq "TRUE") {

src/Octoshift/Factories/GithubApiFactory.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ GithubApi ISourceGithubApiFactory.Create(string apiUrl, string uploadsUrl, strin
3232
uploadsUrl ??= DEFAULT_UPLOADS_URL;
3333
sourcePersonalAccessToken ??= _environmentVariableProvider.SourceGithubPersonalAccessToken();
3434
var githubClient = new GithubClient(_octoLogger, _clientFactory.CreateClient("Default"), _versionProvider, _retryPolicy, _dateTimeProvider, sourcePersonalAccessToken);
35-
var multipartUploader = new ArchiveUploader(githubClient, uploadsUrl, _octoLogger, _retryPolicy);
35+
var multipartUploader = new ArchiveUploader(githubClient, uploadsUrl, _octoLogger, _retryPolicy, _environmentVariableProvider);
3636
return new GithubApi(githubClient, apiUrl, _retryPolicy, multipartUploader);
3737
}
3838

@@ -42,7 +42,7 @@ GithubApi ISourceGithubApiFactory.CreateClientNoSsl(string apiUrl, string upload
4242
uploadsUrl ??= DEFAULT_UPLOADS_URL;
4343
sourcePersonalAccessToken ??= _environmentVariableProvider.SourceGithubPersonalAccessToken();
4444
var githubClient = new GithubClient(_octoLogger, _clientFactory.CreateClient("NoSSL"), _versionProvider, _retryPolicy, _dateTimeProvider, sourcePersonalAccessToken);
45-
var multipartUploader = new ArchiveUploader(githubClient, uploadsUrl, _octoLogger, _retryPolicy);
45+
var multipartUploader = new ArchiveUploader(githubClient, uploadsUrl, _octoLogger, _retryPolicy, _environmentVariableProvider);
4646
return new GithubApi(githubClient, apiUrl, _retryPolicy, multipartUploader);
4747
}
4848

@@ -52,7 +52,7 @@ GithubApi ITargetGithubApiFactory.Create(string apiUrl, string uploadsUrl, strin
5252
uploadsUrl ??= DEFAULT_UPLOADS_URL;
5353
targetPersonalAccessToken ??= _environmentVariableProvider.TargetGithubPersonalAccessToken();
5454
var githubClient = new GithubClient(_octoLogger, _clientFactory.CreateClient("Default"), _versionProvider, _retryPolicy, _dateTimeProvider, targetPersonalAccessToken);
55-
var multipartUploader = new ArchiveUploader(githubClient, uploadsUrl, _octoLogger, _retryPolicy);
55+
var multipartUploader = new ArchiveUploader(githubClient, uploadsUrl, _octoLogger, _retryPolicy, _environmentVariableProvider);
5656
return new GithubApi(githubClient, apiUrl, _retryPolicy, multipartUploader);
5757
}
5858
}

src/Octoshift/Services/ArchiveUploader.cs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,26 @@ namespace OctoshiftCLI.Services;
1111

1212
public class ArchiveUploader
1313
{
14+
private const int BYTES_PER_MEBIBYTE = 1024 * 1024;
15+
private const int MIN_MULTIPART_MEBIBYTES = 5; // 5 MiB minimum size for multipart upload. Don't allow overrides smaller than this.
16+
private const int DEFAULT_MULTIPART_MEBIBYTES = 100;
17+
1418
private readonly GithubClient _client;
1519
private readonly string _uploadsUrl;
1620
private readonly OctoLogger _log;
17-
internal int _streamSizeLimit = 100 * 1024 * 1024; // 100 MiB
21+
private readonly EnvironmentVariableProvider _environmentVariableProvider;
22+
internal int _streamSizeLimit = DEFAULT_MULTIPART_MEBIBYTES * BYTES_PER_MEBIBYTE; // 100 MiB stored in bytes
1823
private readonly RetryPolicy _retryPolicy;
1924

20-
public ArchiveUploader(GithubClient client, string uploadsUrl, OctoLogger log, RetryPolicy retryPolicy)
25+
public ArchiveUploader(GithubClient client, string uploadsUrl, OctoLogger log, RetryPolicy retryPolicy, EnvironmentVariableProvider environmentVariableProvider)
2126
{
2227
_client = client;
2328
_uploadsUrl = uploadsUrl;
2429
_log = log;
2530
_retryPolicy = retryPolicy;
31+
_environmentVariableProvider = environmentVariableProvider;
32+
33+
SetStreamSizeLimitFromEnvironment();
2634
}
2735
public virtual async Task<string> Upload(Stream archiveContent, string archiveName, string orgDatabaseId)
2836
{
@@ -160,4 +168,23 @@ private Uri GetNextUrl(IEnumerable<KeyValuePair<string, IEnumerable<string>>> he
160168
}
161169
throw new OctoshiftCliException("Location header is missing in the response, unable to retrieve next URL for multipart upload.");
162170
}
171+
172+
private void SetStreamSizeLimitFromEnvironment()
173+
{
174+
var envValue = _environmentVariableProvider.GithubOwnedStorageMultipartMebibytes();
175+
if (!int.TryParse(envValue, out var limitInMebibytes) || limitInMebibytes <= 0)
176+
{
177+
return;
178+
}
179+
180+
if (limitInMebibytes < MIN_MULTIPART_MEBIBYTES)
181+
{
182+
_log.LogWarning($"GITHUB_OWNED_STORAGE_MULTIPART_MEBIBYTES is set to {limitInMebibytes} MiB, but the minimum value is {MIN_MULTIPART_MEBIBYTES} MiB. Using default value of {DEFAULT_MULTIPART_MEBIBYTES} MiB.");
183+
return;
184+
}
185+
186+
var limitBytes = (int)((long)limitInMebibytes * BYTES_PER_MEBIBYTE);
187+
_streamSizeLimit = limitBytes;
188+
_log.LogInformation($"Multipart upload part size set to {limitInMebibytes} MiB.");
189+
}
163190
}

src/Octoshift/Services/EnvironmentVariableProvider.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public class EnvironmentVariableProvider
1818
private const string SMB_PASSWORD = "SMB_PASSWORD";
1919
private const string GEI_SKIP_STATUS_CHECK = "GEI_SKIP_STATUS_CHECK";
2020
private const string GEI_SKIP_VERSION_CHECK = "GEI_SKIP_VERSION_CHECK";
21+
private const string GITHUB_OWNED_STORAGE_MULTIPART_MEBIBYTES = "GITHUB_OWNED_STORAGE_MULTIPART_MEBIBYTES";
2122

2223
private readonly OctoLogger _logger;
2324

@@ -65,6 +66,9 @@ public virtual string SkipStatusCheck(bool throwIfNotFound = false) =>
6566
public virtual string SkipVersionCheck(bool throwIfNotFound = false) =>
6667
GetValue(GEI_SKIP_VERSION_CHECK, throwIfNotFound);
6768

69+
public virtual string GithubOwnedStorageMultipartMebibytes(bool throwIfNotFound = false) =>
70+
GetValue(GITHUB_OWNED_STORAGE_MULTIPART_MEBIBYTES, throwIfNotFound);
71+
6872
private string GetValue(string name, bool throwIfNotFound)
6973
{
7074
var value = Environment.GetEnvironmentVariable(name);

src/OctoshiftCLI.IntegrationTests/BbsToGithub.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ public BbsToGithub(ITestOutputHelper output)
6060
_targetGithubHttpClient = new HttpClient();
6161
_targetGithubClient = new GithubClient(_logger, _targetGithubHttpClient, new VersionChecker(_versionClient, _logger), new RetryPolicy(_logger), new DateTimeProvider(), targetGithubToken);
6262
var retryPolicy = new RetryPolicy(_logger);
63-
_archiveUploader = new ArchiveUploader(_targetGithubClient, UPLOADS_URL, _logger, retryPolicy);
63+
var environmentVariableProvider = new EnvironmentVariableProvider(_logger);
64+
_archiveUploader = new ArchiveUploader(_targetGithubClient, UPLOADS_URL, _logger, retryPolicy, environmentVariableProvider);
6465
_targetGithubApi = new GithubApi(_targetGithubClient, "https://api.github.com", new RetryPolicy(_logger), _archiveUploader);
6566

6667
_blobServiceClient = new BlobServiceClient(_azureStorageConnectionString);

src/OctoshiftCLI.IntegrationTests/GhesToGithub.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,11 @@ public GhesToGithub(ITestOutputHelper output)
4949

5050
_versionClient = new HttpClient();
5151
var retryPolicy = new RetryPolicy(logger);
52-
_archiveUploader = new ArchiveUploader(_targetGithubClient, UPLOADS_URL, logger, retryPolicy);
52+
var environmentVariableProvider = new EnvironmentVariableProvider(logger);
5353

5454
_sourceGithubHttpClient = new HttpClient();
5555
_sourceGithubClient = new GithubClient(logger, _sourceGithubHttpClient, new VersionChecker(_versionClient, logger), new RetryPolicy(logger), new DateTimeProvider(), sourceGithubToken);
56+
_archiveUploader = new ArchiveUploader(_targetGithubClient, UPLOADS_URL, logger, retryPolicy, environmentVariableProvider);
5657
_sourceGithubApi = new GithubApi(_sourceGithubClient, GHES_API_URL, new RetryPolicy(logger), _archiveUploader);
5758

5859
_targetGithubHttpClient = new HttpClient();

0 commit comments

Comments
 (0)