Skip to content

Commit c27a94b

Browse files
authored
feat: Support UID/GID when copying files (#1531)
1 parent 7eec4ce commit c27a94b

File tree

34 files changed

+253
-82
lines changed

34 files changed

+253
-82
lines changed

Directory.Packages.props

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
</PropertyGroup>
66
<ItemGroup>
77
<PackageVersion Include="BouncyCastle.Cryptography" Version="2.6.1"/>
8-
<PackageVersion Include="Docker.DotNet.Enhanced.X509" Version="3.128.5"/>
9-
<PackageVersion Include="Docker.DotNet.Enhanced" Version="3.128.5"/>
8+
<PackageVersion Include="Docker.DotNet.Enhanced.X509" Version="3.129.0"/>
9+
<PackageVersion Include="Docker.DotNet.Enhanced" Version="3.129.0"/>
1010
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0"/>
1111
<PackageVersion Include="Microsoft.Bcl.HashCode" Version="1.1.1"/>
1212
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.3"/>
@@ -64,7 +64,7 @@
6464
<PackageVersion Include="InfluxDB.Client" Version="4.18.0"/>
6565
<PackageVersion Include="JanusGraph.Net" Version="1.0.0"/>
6666
<PackageVersion Include="Keycloak.Net.Core" Version="1.0.20"/>
67-
<PackageVersion Include="KubernetesClient" Version="15.0.1"/>
67+
<PackageVersion Include="KubernetesClient" Version="17.0.14"/>
6868
<PackageVersion Include="Microsoft.Azure.Cosmos" Version="3.32.1"/>
6969
<PackageVersion Include="Microsoft.Azure.Kusto.Data" Version="12.2.8"/>
7070
<PackageVersion Include="Microsoft.Data.SqlClient" Version="5.2.2"/>

docs/api/create_docker_container.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,30 @@ _ = new ContainerBuilder()
5858
.WithResourceMapping(Encoding.Default.GetBytes("{}"), "/app/appsettings.json");
5959
```
6060

61+
### Specifying file ownership
62+
63+
When copying files into a container, you can specify the user ID (UID) and group ID (GID) to set the correct ownership of the copied files. This is particularly useful when the container runs as a non-root user or when specific file permissions are required for security or application functionality.
64+
65+
```csharp title="Copying a file with specific UID and GID"
66+
_ = new ContainerBuilder()
67+
.WithResourceMapping(new DirectoryInfo("."), "/app/", uid: 1000, gid: 1000);
68+
```
69+
70+
### Specifying file permission
71+
72+
When copying files into a container, you can specify the file mode to set the correct permissions for the copied files.
73+
74+
```csharp title="Copying a script with executable permissions"
75+
_ = new ContainerBuilder()
76+
.WithResourceMapping(new DirectoryInfo("."), "/app/", fileMode: Unix.FileMode755);
77+
```
78+
79+
The `Unix` class provides common permission configurations like `FileMode755` (read, write, execute for owner; read, execute for group and others). For individual permission combinations, you can use the `UnixFileModes` enumeration to create custom configurations.
80+
81+
### Copying files to a running container
82+
83+
The same UID, GID, and file mode arguments are also available when copying files to already running containers using the `IContainer.CopyAsync(...)` APIs.
84+
6185
## Reading files from the container
6286

6387
The `IContainer` interface offers a `ReadFileAsync(string, CancellationToken)` method to read files from the container. The implementation returns the read bytes. Either process the read bytes in-memory or persist them to the disk.

src/Testcontainers.Cassandra/CassandraContainer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public async Task<ExecResult> ExecScriptAsync(string scriptContent, Cancellation
3535
{
3636
var scriptFilePath = string.Join("/", string.Empty, "tmp", Guid.NewGuid().ToString("D"), Path.GetRandomFileName());
3737

38-
await CopyAsync(Encoding.Default.GetBytes(scriptContent), scriptFilePath, Unix.FileMode644, ct)
38+
await CopyAsync(Encoding.Default.GetBytes(scriptContent), scriptFilePath, fileMode: Unix.FileMode644, ct: ct)
3939
.ConfigureAwait(false);
4040

4141
return await ExecAsync(new[] { "cqlsh", "--file", scriptFilePath }, ct)

src/Testcontainers.ClickHouse/ClickHouseContainer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public async Task<ExecResult> ExecScriptAsync(string scriptContent, Cancellation
4141
{
4242
var scriptFilePath = string.Join("/", string.Empty, "tmp", Guid.NewGuid().ToString("D"), Path.GetRandomFileName());
4343

44-
await CopyAsync(Encoding.Default.GetBytes(scriptContent), scriptFilePath, Unix.FileMode644, ct)
44+
await CopyAsync(Encoding.Default.GetBytes(scriptContent), scriptFilePath, fileMode: Unix.FileMode644, ct: ct)
4545
.ConfigureAwait(false);
4646

4747
return await ExecAsync(new[] { "clickhouse-client", "--database", _configuration.Database, "--queries-file", scriptFilePath }, ct)

src/Testcontainers.CockroachDb/CockroachDbContainer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public async Task<ExecResult> ExecScriptAsync(string scriptContent, Cancellation
4141
{
4242
var scriptFilePath = string.Join("/", string.Empty, "tmp", Guid.NewGuid().ToString("D"), Path.GetRandomFileName());
4343

44-
await CopyAsync(Encoding.Default.GetBytes(scriptContent), scriptFilePath, Unix.FileMode644, ct)
44+
await CopyAsync(Encoding.Default.GetBytes(scriptContent), scriptFilePath, fileMode: Unix.FileMode644, ct: ct)
4545
.ConfigureAwait(false);
4646

4747
return await ExecAsync(new[] { "cockroach", "sql", "--insecure", "--file", scriptFilePath }, ct)

src/Testcontainers.Db2/Db2Container.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public async Task<ExecResult> ExecScriptAsync(string scriptContent, Cancellation
3939

4040
var db2ShellCommand = string.Format(db2ShellCommandFormat, _configuration.Database, _configuration.Username, scriptFilePath);
4141

42-
await CopyAsync(Encoding.Default.GetBytes(scriptContent), scriptFilePath, Unix.FileMode644, ct)
42+
await CopyAsync(Encoding.Default.GetBytes(scriptContent), scriptFilePath, fileMode: Unix.FileMode644, ct: ct)
4343
.ConfigureAwait(false);
4444

4545
return await ExecAsync(new[] { "/bin/sh", "-c", db2ShellCommand}, ct)

src/Testcontainers.FakeGcsServer/FakeGcsServerBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ protected override FakeGcsServerBuilder Init()
6161
// error: HttpStatusCode.NotFound. The HTTP request appears incorrect. The
6262
// container logs indicate the presence of an extra slash: `PUT //upload/storage/v1`.
6363
startupScript.Append("-external-url " + new UriBuilder(Uri.UriSchemeHttp, container.Hostname, container.GetMappedPublicPort(FakeGcsServerPort)).ToString().Trim('/'));
64-
return container.CopyAsync(Encoding.Default.GetBytes(startupScript.ToString()), StartupScriptFilePath, Unix.FileMode755, ct);
64+
return container.CopyAsync(Encoding.Default.GetBytes(startupScript.ToString()), StartupScriptFilePath, fileMode: Unix.FileMode755, ct: ct);
6565
});
6666
}
6767

src/Testcontainers.FirebirdSql/FirebirdSqlContainer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public async Task<ExecResult> ExecScriptAsync(string scriptContent, Cancellation
4141
{
4242
var scriptFilePath = string.Join("/", string.Empty, "tmp", Guid.NewGuid().ToString("D"), Path.GetRandomFileName());
4343

44-
await CopyAsync(Encoding.Default.GetBytes(scriptContent), scriptFilePath, Unix.FileMode644, ct)
44+
await CopyAsync(Encoding.Default.GetBytes(scriptContent), scriptFilePath, fileMode: Unix.FileMode644, ct: ct)
4545
.ConfigureAwait(false);
4646

4747
return await ExecAsync(new[] { "/usr/local/firebird/bin/isql", "-i", scriptFilePath, "-user", _configuration.Username, "-pass", _configuration.Password, _configuration.Database }, ct)

src/Testcontainers.Kafka/KafkaBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ public override KafkaContainer Build()
207207
var startupKafkaBuilder = kafkaBuilder.WithStartupCallback((container, ct) =>
208208
{
209209
var startupScript = vendorConfiguration.CreateStartupScript(kafkaBuilder.DockerResourceConfiguration, container);
210-
return container.CopyAsync(Encoding.Default.GetBytes(startupScript), StartupScriptFilePath, Unix.FileMode755, ct);
210+
return container.CopyAsync(Encoding.Default.GetBytes(startupScript), StartupScriptFilePath, fileMode: Unix.FileMode755, ct: ct);
211211
});
212212

213213
return new KafkaContainer(startupKafkaBuilder.DockerResourceConfiguration);

src/Testcontainers.MariaDb/MariaDbContainer.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public async Task<ExecResult> ExecScriptAsync(string scriptContent, Cancellation
4141
{
4242
var scriptFilePath = string.Join("/", string.Empty, "tmp", Guid.NewGuid().ToString("D"), Path.GetRandomFileName());
4343

44-
await CopyAsync(Encoding.Default.GetBytes(scriptContent), scriptFilePath, Unix.FileMode644, ct)
44+
await CopyAsync(Encoding.Default.GetBytes(scriptContent), scriptFilePath, fileMode: Unix.FileMode644, ct: ct)
4545
.ConfigureAwait(false);
4646

4747
return await ExecAsync(new[] { "mariadb", _configuration.Database, $"--execute=source {scriptFilePath};" }, ct)
@@ -63,6 +63,6 @@ internal Task WriteConfigurationFileAsync(CancellationToken ct = default)
6363
config.WriteLine("protocol=TCP");
6464
config.WriteLine($"user={_configuration.Username}");
6565
config.WriteLine($"password={_configuration.Password}");
66-
return CopyAsync(Encoding.Default.GetBytes(config.ToString()), "/etc/mysql/my.cnf", UnixFileModes.UserRead | UnixFileModes.UserWrite, ct);
66+
return CopyAsync(Encoding.Default.GetBytes(config.ToString()), "/etc/mysql/my.cnf", fileMode: UnixFileModes.UserRead | UnixFileModes.UserWrite, ct: ct);
6767
}
6868
}

0 commit comments

Comments
 (0)