Skip to content

Commit b0f74d4

Browse files
authored
MinIO: A new hosted service to create buckets on startup (#100)
* MinIO: A new hosted service to create buckets on startup Signed-off-by: Victor Chang <vicchang@nvidia.com>
1 parent 91e42e7 commit b0f74d4

20 files changed

+427
-94
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ jobs:
4545
- uses: actions/setup-dotnet@v2
4646
with:
4747
dotnet-version: "6.0.x"
48-
48+
49+
- name: Enable Homebrew
50+
run: echo "/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin" >> $GITHUB_PATH
51+
4952
- name: Install License Finder tool with Homebrew
5053
uses: tecoli-com/actions-use-homebrew-tools@v0
5154
with:

doc/dependency_decisions.yml

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,21 @@
44
- :who: mocsharp
55
:why: Apache-2.0 (https://github.com/aws/aws-sdk-net/raw/master/License.txt)
66
:versions:
7-
- 3.7.13.8
7+
- 3.7.100.1
88
:when: 2022-08-29 18:11:12.923214877 Z
99
- - :approve
1010
- AWSSDK.S3
1111
- :who: mocsharp
1212
:why: Apache-2.0 (https://github.com/aws/aws-sdk-net/raw/master/License.txt)
1313
:versions:
14-
- 3.7.9.57
14+
- 3.7.101.1
1515
:when: 2022-08-29 18:11:13.354973002 Z
1616
- - :approve
1717
- AWSSDK.SecurityToken
1818
- :who: mocsharp
1919
:why: Apache-2.0 (https://github.com/aws/aws-sdk-net/raw/master/License.txt)
2020
:versions:
21-
- 3.7.1.203
21+
- 3.7.100.1
2222
:when: 2022-08-16 18:11:13.781079769 Z
2323
- - :approve
2424
- Ardalis.GuardClauses
@@ -67,7 +67,7 @@
6767
- :who: mocsharp
6868
:why: MIT (https://github.com/microsoft/vstest/raw/v17.3.0/LICENSE)
6969
:versions:
70-
- 17.3.1
70+
- 17.3.2
7171
:when: 2022-08-16 18:11:17.245887971 Z
7272
- - :approve
7373
- Microsoft.Extensions.Configuration
@@ -144,14 +144,14 @@
144144
- :who: mocsharp
145145
:why: MIT (https://github.com/dotnet/aspnetcore/raw/main/LICENSE.txt)
146146
:versions:
147-
- 6.0.9
147+
- 6.0.10
148148
:when: 2022-08-29 18:11:22.090772006 Z
149149
- - :approve
150150
- Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions
151151
- :who: mocsharp
152152
:why: MIT (https://github.com/dotnet/aspnetcore/raw/main/LICENSE.txt)
153153
:versions:
154-
- 6.0.9
154+
- 6.0.10
155155
:when: 2022-08-29 18:11:22.090772006 Z
156156
- - :approve
157157
- Microsoft.Extensions.FileProviders.Abstractions
@@ -263,7 +263,7 @@
263263
- :who: mocsharp
264264
:why: MIT (https://github.com/microsoft/vstest/raw/v17.3.0/LICENSE)
265265
:versions:
266-
- 17.3.1
266+
- 17.3.2
267267
:when: 2022-08-16 18:11:29.155295778 Z
268268
- - :approve
269269
- Microsoft.NETCore.Platforms
@@ -298,14 +298,14 @@
298298
- :who: mocsharp
299299
:why: MIT (https://github.com/microsoft/vstest/raw/v17.3.0/LICENSE)
300300
:versions:
301-
- 17.3.1
301+
- 17.3.2
302302
:when: 2022-08-16 18:11:32.293966383 Z
303303
- - :approve
304304
- Microsoft.TestPlatform.TestHost
305305
- :who: mocsharp
306306
:why: MIT (https://github.com/microsoft/vstest/raw/v17.3.0/LICENSE)
307307
:versions:
308-
- 17.3.1
308+
- 17.3.2
309309
:when: 2022-08-16 18:11:33.162650175 Z
310310
- - :approve
311311
- Microsoft.Win32.Primitives

src/Plugins/AWSS3/Monai.Deploy.Storage.AWSS3.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@
4949

5050
<ItemGroup>
5151
<PackageReference Include="Ardalis.GuardClauses" Version="4.0.1" />
52-
<PackageReference Include="AWSSDK.S3" Version="3.7.9.57" />
53-
<PackageReference Include="AWSSDK.SecurityToken" Version="3.7.1.203" />
52+
<PackageReference Include="AWSSDK.S3" Version="3.7.101.1" />
53+
<PackageReference Include="AWSSDK.SecurityToken" Version="3.7.100.1" />
5454
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
5555
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
5656
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />

src/Plugins/MinIO/ConfigurationKeys.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ internal static class ConfigurationKeys
2828
public static readonly string CredentialServiceUrl = "credentialServiceUrl";
2929
public static readonly string McExecutablePath = "executableLocation";
3030
public static readonly string McServiceName = "serviceName";
31+
public static readonly string CreateBuckets = "createBuckets";
3132

3233
public static readonly string[] RequiredKeys = new[] { EndPoint, AccessKey, AccessToken, SecuredConnection, Region };
3334
public static readonly string[] McRequiredKeys = new[] { EndPoint, AccessKey, AccessToken, McExecutablePath, McServiceName };

src/Plugins/MinIO/IMinIoClientFactory.cs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,22 @@ namespace Monai.Deploy.Storage.MinIO
2121
{
2222
public interface IMinIoClientFactory
2323
{
24-
MinioClient GetClient();
24+
IMinioClient GetClient();
2525

26-
MinioClient GetClient(Credentials credentials);
26+
IMinioClient GetClient(Credentials credentials);
2727

28-
MinioClient GetClient(Credentials credentials, string region);
28+
IMinioClient GetClient(Credentials credentials, string region);
29+
30+
IObjectOperations GetObjectOperationsClient();
31+
32+
IObjectOperations GetObjectOperationsClient(Credentials credentials);
33+
34+
IObjectOperations GetObjectOperationsClient(Credentials credentials, string region);
35+
36+
IBucketOperations GetBucketOperationsClient();
37+
38+
IBucketOperations GetBucketOperationsClient(Credentials credentials);
39+
40+
IBucketOperations GetBucketOperationsClient(Credentials credentials, string region);
2941
}
3042
}

src/Plugins/MinIO/LoggerMethods.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,14 @@ public static partial class LoggerMethods
3434

3535
[LoggerMessage(EventId = 20004, Level = LogLevel.Debug, Message = "Temporary credential policy={policy}.")]
3636
public static partial void TemporaryCredentialPolicy(this ILogger logger, string policy);
37+
38+
[LoggerMessage(EventId = 20005, Level = LogLevel.Information, Message = "`createBuckets` not configured; no buckets created.")]
39+
public static partial void NoBucketCreated(this ILogger logger);
40+
41+
[LoggerMessage(EventId = 20006, Level = LogLevel.Critical, Message = "Error creating bucket {bucket} in region {region}.")]
42+
public static partial void ErrorCreatingBucket(this ILogger logger, string bucket, string region, Exception ex);
43+
44+
[LoggerMessage(EventId = 20007, Level = LogLevel.Information, Message = "Bucket {bucket} created in region {region}.")]
45+
public static partial void BucketCreated(this ILogger logger, string bucket, string region);
3746
}
3847
}

src/Plugins/MinIO/MinIoClientFactory.cs

Lines changed: 69 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -42,47 +42,73 @@ public MinIoClientFactory(IOptions<StorageServiceConfiguration> options)
4242
_clients = new ConcurrentDictionary<string, MinioClient>();
4343
}
4444

45-
public MinioClient GetClient()
45+
public IMinioClient GetClient()
4646
{
4747
return _clients.GetOrAdd(DefaultClient, _ =>
48-
{
49-
var accessKey = Options.Settings[ConfigurationKeys.AccessKey];
50-
var accessToken = Options.Settings[ConfigurationKeys.AccessToken];
51-
var client = CreateClient(accessKey, accessToken);
48+
{
49+
var accessKey = Options.Settings[ConfigurationKeys.AccessKey];
50+
var accessToken = Options.Settings[ConfigurationKeys.AccessToken];
51+
var client = CreateClient(accessKey, accessToken);
5252

53-
return client.Build();
54-
});
53+
return client.Build();
54+
});
5555
}
5656

57-
public MinioClient GetClient(Credentials credentials)
57+
public IMinioClient GetClient(Credentials credentials)
5858
{
5959
return GetClient(credentials, string.Empty);
6060
}
6161

62-
public MinioClient GetClient(Credentials credentials, string region)
62+
public IMinioClient GetClient(Credentials credentials, string region)
6363
{
64-
Guard.Against.Null(credentials, nameof(credentials));
65-
Guard.Against.NullOrWhiteSpace(credentials.AccessKeyId, nameof(credentials.AccessKeyId));
66-
Guard.Against.NullOrWhiteSpace(credentials.SecretAccessKey, nameof(credentials.SecretAccessKey));
67-
Guard.Against.NullOrWhiteSpace(credentials.SessionToken, nameof(credentials.SessionToken));
64+
return GetClientInternal(credentials, region);
65+
}
6866

69-
return _clients.GetOrAdd(credentials.SessionToken, _ =>
67+
public IBucketOperations GetBucketOperationsClient()
68+
{
69+
return _clients.GetOrAdd(DefaultClient, _ =>
7070
{
71-
var client = CreateClient(credentials.AccessKeyId, credentials.SecretAccessKey);
72-
client.WithSessionToken(credentials.SessionToken);
73-
74-
if (!string.IsNullOrWhiteSpace(region))
75-
{
76-
client.WithRegion(region);
77-
}
71+
var accessKey = Options.Settings[ConfigurationKeys.AccessKey];
72+
var accessToken = Options.Settings[ConfigurationKeys.AccessToken];
73+
var client = CreateClient(accessKey, accessToken);
7874

7975
return client.Build();
8076
});
77+
}
8178

79+
public IBucketOperations GetBucketOperationsClient(Credentials credentials)
80+
{
81+
return GetClientInternal(credentials, string.Empty);
82+
}
8283

84+
public IBucketOperations GetBucketOperationsClient(Credentials credentials, string region)
85+
{
86+
return GetClientInternal(credentials, region);
8387
}
8488

85-
private MinioClient CreateClient(string accessKey, string accessToken)
89+
public IObjectOperations GetObjectOperationsClient()
90+
{
91+
return _clients.GetOrAdd(DefaultClient, _ =>
92+
{
93+
var accessKey = Options.Settings[ConfigurationKeys.AccessKey];
94+
var accessToken = Options.Settings[ConfigurationKeys.AccessToken];
95+
var client = CreateClient(accessKey, accessToken);
96+
97+
return client.Build();
98+
});
99+
}
100+
101+
public IObjectOperations GetObjectOperationsClient(Credentials credentials)
102+
{
103+
return GetClientInternal(credentials, string.Empty);
104+
}
105+
106+
public IObjectOperations GetObjectOperationsClient(Credentials credentials, string region)
107+
{
108+
return GetClientInternal(credentials, region);
109+
}
110+
111+
private IMinioClient CreateClient(string accessKey, string accessToken)
86112
{
87113
var endpoint = Options.Settings[ConfigurationKeys.EndPoint];
88114
var securedConnection = Options.Settings[ConfigurationKeys.SecuredConnection];
@@ -99,6 +125,27 @@ private MinioClient CreateClient(string accessKey, string accessToken)
99125
return client;
100126
}
101127

128+
private MinioClient GetClientInternal(Credentials credentials, string region)
129+
{
130+
Guard.Against.Null(credentials, nameof(credentials));
131+
Guard.Against.NullOrWhiteSpace(credentials.AccessKeyId, nameof(credentials.AccessKeyId));
132+
Guard.Against.NullOrWhiteSpace(credentials.SecretAccessKey, nameof(credentials.SecretAccessKey));
133+
Guard.Against.NullOrWhiteSpace(credentials.SessionToken, nameof(credentials.SessionToken));
134+
135+
return _clients.GetOrAdd(credentials.SessionToken, _ =>
136+
{
137+
var client = CreateClient(credentials.AccessKeyId, credentials.SecretAccessKey);
138+
client.WithSessionToken(credentials.SessionToken);
139+
140+
if (!string.IsNullOrWhiteSpace(region))
141+
{
142+
client.WithRegion(region);
143+
}
144+
145+
return client.Build();
146+
});
147+
}
148+
102149
private void ValidateConfiguration(StorageServiceConfiguration configuration)
103150
{
104151
Guard.Against.Null(configuration, nameof(configuration));

src/Plugins/MinIO/MinIoHealthCheck.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public MinIoHealthCheck(IMinIoClientFactory minIoClientFactory, ILogger<MinIoHea
3434
{
3535
try
3636
{
37-
var minioClient = _minIoClientFactory.GetClient();
37+
var minioClient = _minIoClientFactory.GetBucketOperationsClient();
3838
await minioClient.ListBucketsAsync(cancellationToken).ConfigureAwait(false);
3939

4040
return HealthCheckResult.Healthy();

src/Plugins/MinIO/MinIoStartup.cs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright 2022 MONAI Consortium
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
using Ardalis.GuardClauses;
18+
using Microsoft.Extensions.Hosting;
19+
using Microsoft.Extensions.Logging;
20+
using Microsoft.Extensions.Options;
21+
using Minio;
22+
using Monai.Deploy.Storage.Configuration;
23+
24+
namespace Monai.Deploy.Storage.MinIO
25+
{
26+
public class MinIoStartup : IHostedService
27+
{
28+
private readonly IMinIoClientFactory _minIoClientFactory;
29+
private readonly IOptions<StorageServiceConfiguration> _options;
30+
private readonly ILogger<MinIoStartup> _logger;
31+
32+
public MinIoStartup(
33+
IMinIoClientFactory minIoClientFactory,
34+
IOptions<StorageServiceConfiguration> options,
35+
ILogger<MinIoStartup> logger)
36+
{
37+
_minIoClientFactory = minIoClientFactory ?? throw new ArgumentNullException(nameof(minIoClientFactory));
38+
_options = options ?? throw new ArgumentNullException(nameof(options));
39+
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
40+
}
41+
42+
public async Task StartAsync(CancellationToken cancellationToken)
43+
{
44+
if (_options.Value.Settings.ContainsKey(ConfigurationKeys.CreateBuckets))
45+
{
46+
var buckets = _options.Value.Settings[ConfigurationKeys.CreateBuckets];
47+
var region = _options.Value.Settings.ContainsKey(ConfigurationKeys.Region) ? _options.Value.Settings[ConfigurationKeys.Region] : string.Empty;
48+
49+
if (!string.IsNullOrWhiteSpace(buckets))
50+
{
51+
var exceptions = new List<Exception>();
52+
var bucketNames = buckets.Split(',', StringSplitOptions.RemoveEmptyEntries);
53+
54+
var client = _minIoClientFactory.GetBucketOperationsClient();
55+
56+
foreach (var bucket in bucketNames)
57+
{
58+
try
59+
{
60+
await CreateBucket(client, bucket.Trim(), region, cancellationToken).ConfigureAwait(false);
61+
}
62+
catch (Exception ex)
63+
{
64+
_logger.ErrorCreatingBucket(bucket, region, ex);
65+
exceptions.Add(ex);
66+
}
67+
}
68+
69+
if (exceptions.Any())
70+
{
71+
throw new AggregateException("Error creating buckets.", exceptions);
72+
}
73+
}
74+
}
75+
else
76+
{
77+
_logger.NoBucketCreated();
78+
}
79+
}
80+
81+
public Task StopAsync(CancellationToken cancellationToken)
82+
{
83+
return Task.CompletedTask;
84+
}
85+
86+
private async Task CreateBucket(IBucketOperations client, string bucket, string region, CancellationToken cancellationToken)
87+
{
88+
Guard.Against.Null(client, nameof(client));
89+
Guard.Against.Null(bucket, nameof(bucket));
90+
91+
var bucketExistsArgs = new BucketExistsArgs().WithBucket(bucket);
92+
if (!await client.BucketExistsAsync(bucketExistsArgs, cancellationToken).ConfigureAwait(false))
93+
{
94+
var makeBucketArgs = new MakeBucketArgs().WithBucket(bucket);
95+
if (!string.IsNullOrWhiteSpace(region))
96+
{
97+
makeBucketArgs.WithLocation(region);
98+
}
99+
await client.MakeBucketAsync(makeBucketArgs, cancellationToken).ConfigureAwait(false);
100+
_logger.BucketCreated(bucket, region);
101+
}
102+
}
103+
}
104+
}

0 commit comments

Comments
 (0)