Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion RELEASENOTES.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@

- Added support for configurable multipart upload chunk size for GitHub-owned storage uploads via `GITHUB_OWNED_STORAGE_MULTIPART_BYTES` environment variable (minimum 5 MiB, default 100 MiB) to improve upload reliability in environments with proxies or slow connections
6 changes: 3 additions & 3 deletions src/Octoshift/Factories/GithubApiFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ GithubApi ISourceGithubApiFactory.Create(string apiUrl, string sourcePersonalAcc
apiUrl ??= DEFAULT_API_URL;
sourcePersonalAccessToken ??= _environmentVariableProvider.SourceGithubPersonalAccessToken();
var githubClient = new GithubClient(_octoLogger, _clientFactory.CreateClient("Default"), _versionProvider, _retryPolicy, _dateTimeProvider, sourcePersonalAccessToken);
var multipartUploader = new ArchiveUploader(githubClient, _octoLogger, _retryPolicy);
var multipartUploader = new ArchiveUploader(githubClient, _octoLogger, _retryPolicy, _environmentVariableProvider);
return new GithubApi(githubClient, apiUrl, _retryPolicy, multipartUploader);
}

Expand All @@ -39,7 +39,7 @@ GithubApi ISourceGithubApiFactory.CreateClientNoSsl(string apiUrl, string source
apiUrl ??= DEFAULT_API_URL;
sourcePersonalAccessToken ??= _environmentVariableProvider.SourceGithubPersonalAccessToken();
var githubClient = new GithubClient(_octoLogger, _clientFactory.CreateClient("NoSSL"), _versionProvider, _retryPolicy, _dateTimeProvider, sourcePersonalAccessToken);
var multipartUploader = new ArchiveUploader(githubClient, _octoLogger, _retryPolicy);
var multipartUploader = new ArchiveUploader(githubClient, _octoLogger, _retryPolicy, _environmentVariableProvider);
return new GithubApi(githubClient, apiUrl, _retryPolicy, multipartUploader);
}

Expand All @@ -48,7 +48,7 @@ GithubApi ITargetGithubApiFactory.Create(string apiUrl, string targetPersonalAcc
apiUrl ??= DEFAULT_API_URL;
targetPersonalAccessToken ??= _environmentVariableProvider.TargetGithubPersonalAccessToken();
var githubClient = new GithubClient(_octoLogger, _clientFactory.CreateClient("Default"), _versionProvider, _retryPolicy, _dateTimeProvider, targetPersonalAccessToken);
var multipartUploader = new ArchiveUploader(githubClient, _octoLogger, _retryPolicy);
var multipartUploader = new ArchiveUploader(githubClient, _octoLogger, _retryPolicy, _environmentVariableProvider);
return new GithubApi(githubClient, apiUrl, _retryPolicy, multipartUploader);
}
}
26 changes: 25 additions & 1 deletion src/Octoshift/Services/ArchiveUploader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,24 @@ namespace OctoshiftCLI.Services;

public class ArchiveUploader
{
private const int MIN_MULTIPART_BYTES = 5 * 1024 * 1024; // 5 MiB minimum size for multipart upload. Don't allow overrides smaller than this.

private readonly GithubClient _client;
private readonly OctoLogger _log;
private readonly EnvironmentVariableProvider _environmentVariableProvider;
internal int _streamSizeLimit = 100 * 1024 * 1024; // 100 MiB
private readonly RetryPolicy _retryPolicy;

private const string BASE_URL = "https://uploads.github.com";

public ArchiveUploader(GithubClient client, OctoLogger log, RetryPolicy retryPolicy)
public ArchiveUploader(GithubClient client, OctoLogger log, RetryPolicy retryPolicy, EnvironmentVariableProvider environmentVariableProvider)
{
_client = client;
_log = log;
_retryPolicy = retryPolicy;
_environmentVariableProvider = environmentVariableProvider;

SetStreamSizeLimitFromEnvironment();
}
public virtual async Task<string> Upload(Stream archiveContent, string archiveName, string orgDatabaseId)
{
Expand Down Expand Up @@ -160,4 +166,22 @@ private Uri GetNextUrl(IEnumerable<KeyValuePair<string, IEnumerable<string>>> he
}
throw new OctoshiftCliException("Location header is missing in the response, unable to retrieve next URL for multipart upload.");
}

private void SetStreamSizeLimitFromEnvironment()
{
var envValue = _environmentVariableProvider.GithubOwnedStorageMultipartBytes();
if (!int.TryParse(envValue, out var limit) || limit <= 0)
{
return;
}

if (limit < MIN_MULTIPART_BYTES)
{
_log.LogWarning($"GITHUB_OWNED_STORAGE_MULTIPART_BYTES is set to {limit} bytes, but the minimum value is {MIN_MULTIPART_BYTES} bytes. Using default value of {_streamSizeLimit} bytes.");
return;
}

_streamSizeLimit = limit;
_log.LogInformation($"Stream size limit set to {_streamSizeLimit} bytes.");
}
}
4 changes: 4 additions & 0 deletions src/Octoshift/Services/EnvironmentVariableProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class EnvironmentVariableProvider
private const string SMB_PASSWORD = "SMB_PASSWORD";
private const string GEI_SKIP_STATUS_CHECK = "GEI_SKIP_STATUS_CHECK";
private const string GEI_SKIP_VERSION_CHECK = "GEI_SKIP_VERSION_CHECK";
private const string GITHUB_OWNED_STORAGE_MULTIPART_BYTES = "GITHUB_OWNED_STORAGE_MULTIPART_BYTES";

private readonly OctoLogger _logger;

Expand Down Expand Up @@ -65,6 +66,9 @@ public virtual string SkipStatusCheck(bool throwIfNotFound = false) =>
public virtual string SkipVersionCheck(bool throwIfNotFound = false) =>
GetValue(GEI_SKIP_VERSION_CHECK, throwIfNotFound);

public virtual string GithubOwnedStorageMultipartBytes(bool throwIfNotFound = false) =>
GetValue(GITHUB_OWNED_STORAGE_MULTIPART_BYTES, throwIfNotFound);

private string GetValue(string name, bool throwIfNotFound)
{
var value = Environment.GetEnvironmentVariable(name);
Expand Down
3 changes: 2 additions & 1 deletion src/OctoshiftCLI.IntegrationTests/BbsToGithub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ public BbsToGithub(ITestOutputHelper output)
_targetGithubHttpClient = new HttpClient();
_targetGithubClient = new GithubClient(_logger, _targetGithubHttpClient, new VersionChecker(_versionClient, _logger), new RetryPolicy(_logger), new DateTimeProvider(), targetGithubToken);
var retryPolicy = new RetryPolicy(_logger);
_archiveUploader = new ArchiveUploader(_targetGithubClient, _logger, retryPolicy);
var environmentVariableProvider = new EnvironmentVariableProvider(_logger);
_archiveUploader = new ArchiveUploader(_targetGithubClient, _logger, retryPolicy, environmentVariableProvider);
_targetGithubApi = new GithubApi(_targetGithubClient, "https://api.github.com", new RetryPolicy(_logger), _archiveUploader);

_blobServiceClient = new BlobServiceClient(_azureStorageConnectionString);
Expand Down
3 changes: 2 additions & 1 deletion src/OctoshiftCLI.IntegrationTests/GhesToGithub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,11 @@ public GhesToGithub(ITestOutputHelper output)

_versionClient = new HttpClient();
var retryPolicy = new RetryPolicy(logger);
_archiveUploader = new ArchiveUploader(_targetGithubClient, logger, retryPolicy);
var environmentVariableProvider = new EnvironmentVariableProvider(logger);

_sourceGithubHttpClient = new HttpClient();
_sourceGithubClient = new GithubClient(logger, _sourceGithubHttpClient, new VersionChecker(_versionClient, logger), new RetryPolicy(logger), new DateTimeProvider(), sourceGithubToken);
_archiveUploader = new ArchiveUploader(_sourceGithubClient, logger, retryPolicy, environmentVariableProvider);
_sourceGithubApi = new GithubApi(_sourceGithubClient, GHES_API_URL, new RetryPolicy(logger), _archiveUploader);

_targetGithubHttpClient = new HttpClient();
Expand Down
Loading
Loading