Skip to content

Commit 0063cd9

Browse files
authored
Merge pull request #1479 from github/brianaj/external-pr-1403
External pr 1403: Support configurable GHOS multipart upload chunk size
2 parents d141fb1 + 1073275 commit 0063cd9

File tree

13 files changed

+257
-24
lines changed

13 files changed

+257
-24
lines changed

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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1+
- 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

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)