Skip to content

Commit 2eb6c0a

Browse files
committed
Change the value of the multipart upload storage override to medibytes instead of bytes
env variable is now called GITHUB_OWNED_STORAGE_MULTIPART_MEBIBYTES
1 parent daa636d commit 2eb6c0a

File tree

5 files changed

+60
-35
lines changed

5 files changed

+60
-35
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-
- 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
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/Services/ArchiveUploader.cs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ namespace OctoshiftCLI.Services;
1111

1212
public class ArchiveUploader
1313
{
14-
private const int MIN_MULTIPART_BYTES = 5 * 1024 * 1024; // 5 MiB minimum size for multipart upload. Don't allow overrides smaller than this.
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;
1517

1618
private readonly GithubClient _client;
1719
private readonly string _uploadsUrl;
1820
private readonly OctoLogger _log;
1921
private readonly EnvironmentVariableProvider _environmentVariableProvider;
20-
internal int _streamSizeLimit = 100 * 1024 * 1024; // 100 MiB
22+
internal int _streamSizeLimit = DEFAULT_MULTIPART_MEBIBYTES * BYTES_PER_MEBIBYTE; // 100 MiB stored in bytes
2123
private readonly RetryPolicy _retryPolicy;
2224

2325
public ArchiveUploader(GithubClient client, string uploadsUrl, OctoLogger log, RetryPolicy retryPolicy, EnvironmentVariableProvider environmentVariableProvider)
@@ -169,19 +171,20 @@ private Uri GetNextUrl(IEnumerable<KeyValuePair<string, IEnumerable<string>>> he
169171

170172
private void SetStreamSizeLimitFromEnvironment()
171173
{
172-
var envValue = _environmentVariableProvider.GithubOwnedStorageMultipartBytes();
173-
if (!int.TryParse(envValue, out var limit) || limit <= 0)
174+
var envValue = _environmentVariableProvider.GithubOwnedStorageMultipartMebibytes();
175+
if (!int.TryParse(envValue, out var limitInMebibytes) || limitInMebibytes <= 0)
174176
{
175177
return;
176178
}
177179

178-
if (limit < MIN_MULTIPART_BYTES)
180+
if (limitInMebibytes < MIN_MULTIPART_MEBIBYTES)
179181
{
180-
_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.");
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.");
181183
return;
182184
}
183185

184-
_streamSizeLimit = limit;
185-
_log.LogInformation($"Multipart upload part size set to {((long)_streamSizeLimit).ToLogFriendlySize()}.");
186+
var limitBytes = (int)((long)limitInMebibytes * BYTES_PER_MEBIBYTE);
187+
_streamSizeLimit = limitBytes;
188+
_log.LogInformation($"Multipart upload part size set to {limitInMebibytes} MB.");
186189
}
187190
}

src/Octoshift/Services/EnvironmentVariableProvider.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +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_BYTES = "GITHUB_OWNED_STORAGE_MULTIPART_BYTES";
21+
private const string GITHUB_OWNED_STORAGE_MULTIPART_MEBIBYTES = "GITHUB_OWNED_STORAGE_MULTIPART_MEBIBYTES";
2222

2323
private readonly OctoLogger _logger;
2424

@@ -66,8 +66,8 @@ public virtual string SkipStatusCheck(bool throwIfNotFound = false) =>
6666
public virtual string SkipVersionCheck(bool throwIfNotFound = false) =>
6767
GetValue(GEI_SKIP_VERSION_CHECK, throwIfNotFound);
6868

69-
public virtual string GithubOwnedStorageMultipartBytes(bool throwIfNotFound = false) =>
70-
GetValue(GITHUB_OWNED_STORAGE_MULTIPART_BYTES, throwIfNotFound);
69+
public virtual string GithubOwnedStorageMultipartMebibytes(bool throwIfNotFound = false) =>
70+
GetValue(GITHUB_OWNED_STORAGE_MULTIPART_MEBIBYTES, throwIfNotFound);
7171

7272
private string GetValue(string name, bool throwIfNotFound)
7373
{

src/OctoshiftCLI.Tests/Octoshift/Services/ArchiveUploadersTests.cs

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -46,21 +46,22 @@ public async Task Upload_Should_Throw_ArgumentNullException_When_Archive_Content
4646
public void Constructor_Should_Use_Valid_Environment_Variable_Value()
4747
{
4848
// Arrange
49-
var customSize = 10 * 1024 * 1024; // 10 MiB
49+
var customSizeMiB = 10; // 10 MiB
50+
var customSizeBytes = customSizeMiB * 1024 * 1024;
5051
var logMock = TestHelpers.CreateMock<OctoLogger>();
5152
var githubClientMock = TestHelpers.CreateMock<GithubClient>();
5253
var environmentVariableProviderMock = TestHelpers.CreateMock<EnvironmentVariableProvider>();
5354
var retryPolicy = new RetryPolicy(logMock.Object);
5455

5556
environmentVariableProviderMock
56-
.Setup(x => x.GithubOwnedStorageMultipartBytes(false))
57-
.Returns(customSize.ToString());
57+
.Setup(x => x.GithubOwnedStorageMultipartMebibytes(false))
58+
.Returns(customSizeMiB.ToString());
5859

5960
// Act
6061
var archiveUploader = new ArchiveUploader(_githubClientMock.Object, UPLOADS_URL, logMock.Object, retryPolicy, environmentVariableProviderMock.Object);
6162

6263
// Assert
63-
archiveUploader._streamSizeLimit.Should().Be(customSize);
64+
archiveUploader._streamSizeLimit.Should().Be(customSizeBytes);
6465
logMock.Verify(x => x.LogInformation($"Multipart upload part size set to 10 MB."), Times.Once);
6566
}
6667

@@ -75,7 +76,7 @@ public void Constructor_Should_Use_Default_When_Environment_Variable_Not_Set()
7576
var retryPolicy = new RetryPolicy(logMock.Object);
7677

7778
environmentVariableProviderMock
78-
.Setup(x => x.GithubOwnedStorageMultipartBytes(false))
79+
.Setup(x => x.GithubOwnedStorageMultipartMebibytes(false))
7980
.Returns(() => null);
8081

8182
// Act
@@ -96,7 +97,7 @@ public void Constructor_Should_Use_Default_When_Environment_Variable_Is_Invalid(
9697
var retryPolicy = new RetryPolicy(logMock.Object);
9798

9899
environmentVariableProviderMock
99-
.Setup(x => x.GithubOwnedStorageMultipartBytes(false))
100+
.Setup(x => x.GithubOwnedStorageMultipartMebibytes(false))
100101
.Returns("invalid_value");
101102

102103
// Act
@@ -117,7 +118,7 @@ public void Constructor_Should_Use_Default_When_Environment_Variable_Is_Zero()
117118
var retryPolicy = new RetryPolicy(logMock.Object);
118119

119120
environmentVariableProviderMock
120-
.Setup(x => x.GithubOwnedStorageMultipartBytes(false))
121+
.Setup(x => x.GithubOwnedStorageMultipartMebibytes(false))
121122
.Returns("0");
122123

123124
// Act
@@ -138,7 +139,7 @@ public void Constructor_Should_Use_Default_When_Environment_Variable_Is_Negative
138139
var retryPolicy = new RetryPolicy(logMock.Object);
139140

140141
environmentVariableProviderMock
141-
.Setup(x => x.GithubOwnedStorageMultipartBytes(false))
142+
.Setup(x => x.GithubOwnedStorageMultipartMebibytes(false))
142143
.Returns("-1000");
143144

144145
// Act
@@ -152,67 +153,70 @@ public void Constructor_Should_Use_Default_When_Environment_Variable_Is_Negative
152153
public void Constructor_Should_Use_Default_And_Log_Warning_When_Environment_Variable_Below_Minimum()
153154
{
154155
// Arrange
155-
var belowMinimumSize = 1024 * 1024; // 1 MiB (below 5 MiB minimum)
156-
var defaultSize = 100 * 1024 * 1024; // 100 MiB
157-
var minSize = 5 * 1024 * 1024; // 5 MiB minimum
156+
var belowMinimumSizeMiB = 1; // below 5 MiB minimum
157+
var defaultSizeMiB = 100;
158+
var defaultSizeBytes = defaultSizeMiB * 1024 * 1024;
159+
var minSizeMiB = 5; // 5 MiB minimum
158160
var logMock = TestHelpers.CreateMock<OctoLogger>();
159161
var githubClientMock = TestHelpers.CreateMock<GithubClient>();
160162
var environmentVariableProviderMock = TestHelpers.CreateMock<EnvironmentVariableProvider>();
161163
var retryPolicy = new RetryPolicy(logMock.Object);
162164

163165
environmentVariableProviderMock
164-
.Setup(x => x.GithubOwnedStorageMultipartBytes(false))
165-
.Returns(belowMinimumSize.ToString());
166+
.Setup(x => x.GithubOwnedStorageMultipartMebibytes(false))
167+
.Returns(belowMinimumSizeMiB.ToString());
166168

167169
// Act
168170
var archiveUploader = new ArchiveUploader(_githubClientMock.Object, UPLOADS_URL, logMock.Object, retryPolicy, environmentVariableProviderMock.Object);
169171

170172
// Assert
171-
archiveUploader._streamSizeLimit.Should().Be(defaultSize);
172-
logMock.Verify(x => x.LogWarning($"GITHUB_OWNED_STORAGE_MULTIPART_BYTES is set to {belowMinimumSize} bytes, but the minimum value is {minSize} bytes. Using default value of {defaultSize} bytes."), Times.Once);
173+
archiveUploader._streamSizeLimit.Should().Be(defaultSizeBytes);
174+
logMock.Verify(x => x.LogWarning($"GITHUB_OWNED_STORAGE_MULTIPART_MEBIBYTES is set to {belowMinimumSizeMiB} MiB, but the minimum value is {minSizeMiB} MiB. Using default value of {defaultSizeMiB} MiB."), Times.Once);
173175
}
174176

175177
[Fact]
176178
public void Constructor_Should_Accept_Value_Equal_To_Minimum()
177179
{
178180
// Arrange
179-
var minimumSize = 5 * 1024 * 1024; // 5 MiB minimum
181+
var minimumSizeMiB = 5; // 5 MiB minimum
182+
var minimumSizeBytes = minimumSizeMiB * 1024 * 1024;
180183
var logMock = TestHelpers.CreateMock<OctoLogger>();
181184
var githubClientMock = TestHelpers.CreateMock<GithubClient>();
182185
var environmentVariableProviderMock = TestHelpers.CreateMock<EnvironmentVariableProvider>();
183186
var retryPolicy = new RetryPolicy(logMock.Object);
184187

185188
environmentVariableProviderMock
186-
.Setup(x => x.GithubOwnedStorageMultipartBytes(false))
187-
.Returns(minimumSize.ToString());
189+
.Setup(x => x.GithubOwnedStorageMultipartMebibytes(false))
190+
.Returns(minimumSizeMiB.ToString());
188191

189192
// Act
190193
var archiveUploader = new ArchiveUploader(_githubClientMock.Object, UPLOADS_URL, logMock.Object, retryPolicy, environmentVariableProviderMock.Object);
191194

192195
// Assert
193-
archiveUploader._streamSizeLimit.Should().Be(minimumSize);
196+
archiveUploader._streamSizeLimit.Should().Be(minimumSizeBytes);
194197
logMock.Verify(x => x.LogInformation($"Multipart upload part size set to 5 MB."), Times.Once);
195198
}
196199

197200
[Fact]
198201
public void Constructor_Should_Accept_Large_Valid_Value()
199202
{
200203
// Arrange
201-
var largeSize = 500 * 1024 * 1024; // 500 MiB
204+
var largeSizeMiB = 500; // 500 MiB
205+
var largeSizeBytes = largeSizeMiB * 1024 * 1024;
202206
var logMock = TestHelpers.CreateMock<OctoLogger>();
203207
var githubClientMock = TestHelpers.CreateMock<GithubClient>();
204208
var environmentVariableProviderMock = TestHelpers.CreateMock<EnvironmentVariableProvider>();
205209
var retryPolicy = new RetryPolicy(logMock.Object);
206210

207211
environmentVariableProviderMock
208-
.Setup(x => x.GithubOwnedStorageMultipartBytes(false))
209-
.Returns(largeSize.ToString());
212+
.Setup(x => x.GithubOwnedStorageMultipartMebibytes(false))
213+
.Returns(largeSizeMiB.ToString());
210214

211215
// Act
212216
var archiveUploader = new ArchiveUploader(githubClientMock.Object, UPLOADS_URL, logMock.Object, retryPolicy, environmentVariableProviderMock.Object);
213217

214218
// Assert
215-
archiveUploader._streamSizeLimit.Should().Be(largeSize);
219+
archiveUploader._streamSizeLimit.Should().Be(largeSizeBytes);
216220
logMock.Verify(x => x.LogInformation($"Multipart upload part size set to 500 MB."), Times.Once);
217221
}
218222

0 commit comments

Comments
 (0)