testcontainers / testcontainers-dotnet

A library to support tests with throwaway instances of Docker containers for all compatible .NET Standard versions.
https://dotnet.testcontainers.org
MIT License
3.65k stars 250 forks source link

[Bug]: problem with exception in .dockerignore #1119

Closed Hogedal closed 4 months ago

Hogedal commented 4 months ago

Testcontainers version

3.7.0

Using the latest Testcontainers version?

Yes

Host OS

windows

Host arch

x64

.NET version

8.0.0

Docker version

Client:
 Cloud integration: v1.0.25
 Version:           20.10.16
 API version:       1.41
 Go version:        go1.17.10
 Git commit:        aa7e414
 Built:             Thu May 12 09:17:07 2022
 OS/Arch:           windows/amd64
 Context:           default
 Experimental:      true

Server: Docker Desktop 4.9.1 (81317)
 Engine:
  Version:          20.10.16
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.17.10
  Git commit:       f756502
  Built:            Thu May 12 09:15:42 2022
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.6.4
  GitCommit:        212e8b6fa2f44b9c21b2798135fc6fb7c53efc16
 runc:
  Version:          1.1.1
  GitCommit:        v1.1.1-0-g52de29d
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

Docker info

Client:
 Context:    default
 Debug Mode: false
 Plugins:
  buildx: Docker Buildx (Docker Inc., v0.8.2)
  compose: Docker Compose (Docker Inc., v2.6.0)
  sbom: View the packaged-based Software Bill Of Materials (SBOM) for an image (Anchore Inc., 0.6.0)
  scan: Docker Scan (Docker Inc., v0.17.0)

Server:
 Containers: 3
  Running: 0
  Paused: 0
  Stopped: 3
 Images: 18
 Server Version: 20.10.16
 Storage Driver: overlay2
  Backing Filesystem: extfs
  Supports d_type: true
  Native Overlay Diff: true
  userxattr: false
 Logging Driver: json-file
 Cgroup Driver: cgroupfs
 Cgroup Version: 1
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
 Swarm: inactive
 Runtimes: io.containerd.runc.v2 io.containerd.runtime.v1.linux runc
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: 212e8b6fa2f44b9c21b2798135fc6fb7c53efc16
 runc version: v1.1.1-0-g52de29d
 init version: de40ad0
 Security Options:
  seccomp
   Profile: default
 Kernel Version: 5.10.102.1-microsoft-standard-WSL2
 Operating System: Docker Desktop
 OSType: linux
 Architecture: x86_64
 CPUs: 24
 Total Memory: 25.01GiB
 Name: docker-desktop
 ID: 7RXC:EYC2:BSJZ:2YON:A2LB:TAJU:HONG:KGHT:4Z27:U4K6:MVIO:OZIU
 Docker Root Dir: /var/lib/docker
 Debug Mode: false
 HTTP Proxy: http.docker.internal:3128
 HTTPS Proxy: http.docker.internal:3128
 No Proxy: hubproxy.docker.internal
 Registry: https://index.docker.io/v1/
 Labels:
 Experimental: false
 Insecure Registries:
  hubproxy.docker.internal:5000
  127.0.0.0/8
 Live Restore Enabled: false

WARNING: No blkio throttle.read_bps_device support
WARNING: No blkio throttle.write_bps_device support
WARNING: No blkio throttle.read_iops_device support
WARNING: No blkio throttle.write_iops_device support

What happened?

I am attempting to build a new container image on the fly as such:

 var salesOrderServiceHealthCheck = new ImageFromDockerfileBuilder()
     .WithDockerfileDirectory(CommonDirectoryPath.GetSolutionDirectory(), "")
     .WithDockerfile("SalesOrderService/Dockerfile")
     .Build();
 await salesOrderServiceHealthCheck.CreateAsync()
     .ConfigureAwait(false);

 var salesOrderService = new ContainerBuilder()
     .WithName("salesOrderService-" + testId)
     .WithImage(salesOrderServiceHealthCheck)
     .WithPortBinding(8080, true)
     //Nservicebus
     .WithEnvironment("some environment vars")
     .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(8080))
     .Build();

And I have a visual studio generated .dockerignore which looks like this

**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
!**/.gitignore
!.git/HEAD
!.git/config
!.git/packed-refs
!.git/refs/heads/**

If I try to run this container with that dockerignore I get an error relating to a file in the .vs folder already being in use, despite **/.vs being in the ignore list. Removing the 5 exception lines at the bottom resolves the issue, but that may not be a viable solution in all cases.

Relevant log output

 OrderSagaTest
   Source: OrderServiceSalesOrderService.cs line 91
   Duration: 12,3 sec

  Message: 
Initialization method Andel.Energi.GSOF.Component.Integration.Test.OrderServiceSalesOrderService.OrderServiceSalesOrderService.Initialize threw exception. System.IO.IOException: Cannot create Docker image tar archive. ---> System.IO.IOException: The process cannot access the file 'C:\Lab\GitLocalRepos\GasSupplyOrderFulfillment\src\.vs\Andel.Energi.GSOF\v17\TestStore\0\002.testlog' because it is being used by another process..

  Stack Trace: 
SafeFileHandle.CreateFile(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options)
SafeFileHandle.Open(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
OSFileStreamStrategy.ctor(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
DockerfileArchive.Tar(CancellationToken ct)
--- End of inner exception stack trace ---
DockerfileArchive.Tar(CancellationToken ct)
DockerImageOperations.BuildAsync(IImageFromDockerfileConfiguration configuration, ITarArchive dockerfileArchive, CancellationToken ct)
TestcontainersClient.BuildAsync(IImageFromDockerfileConfiguration configuration, CancellationToken ct)
FutureDockerImage.UnsafeCreateAsync(CancellationToken ct)
FutureDockerImage.CreateAsync(CancellationToken ct)
OrderServiceSalesOrderService.Initialize() line 56

  Standard Output: 
[testcontainers.org 00:00:00.05] Connected to Docker:
  Host: npipe://./pipe/docker_engine
  Server Version: 25.0.2
  Kernel Version: 5.10.102.1-microsoft-standard-WSL2
  API Version: 1.44
  Operating System: Docker Desktop
  Total Memory: 24.79 GB
[testcontainers.org 00:00:00.27] Pattern ^(.+)\/\.idea added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(\.idea\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?((.+)\/\.vs\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(\.vs\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?((.+)\/\.classpath\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(\.classpath\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?((.+)\/\.dockerignore\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(\.dockerignore\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?((.+)\/\.env\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(\.env\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?((.+)\/\.git\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(\.git\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?((.+)\/\.gitignore\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(\.gitignore\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?((.+)\/\.project\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(\.project\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?((.+)\/\.settings\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(\.settings\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?((.+)\/\.toolstarget\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(\.toolstarget\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?((.+)\/\.vs\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(\.vs\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?((.+)\/\.vscode\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(\.vscode\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?((.+)\/([^\\\/]+)\.([^\\\/]+)?proj\.user\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(([^\\\/]+)\.([^\\\/]+)?proj\.user\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?((.+)\/([^\\\/]+)?\.dbmdl\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(([^\\\/]+)?\.dbmdl\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?((.+)\/([^\\\/]+)?\.jfm\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(([^\\\/]+)?\.jfm\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?((.+)\/azds\.yaml\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(azds\.yaml\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?((.+)\/bin\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(bin\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?((.+)\/charts\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(charts\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?((.+)\/docker\-compose([^\\\/]+)?$\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(docker\-compose([^\\\/]+)?$\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?((.+)\/Dockerfile([^\\\/]+)?$\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(Dockerfile([^\\\/]+)?$\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?((.+)\/node_modules\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(node_modules\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?((.+)\/npm\-debug\.log\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(npm\-debug\.log\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?((.+)\/obj\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(obj\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?((.+)\/secrets\.dev\.yaml\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(secrets\.dev\.yaml\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?((.+)\/values\.dev\.yaml\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(values\.dev\.yaml\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(LICENSE\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(README\.md\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?((.+)\/\.gitignore\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(\.gitignore\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(\.git\/HEAD\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(HEAD\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(\.git\/config\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(config\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(\.git\/packed\-refs\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(packed\-refs\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(\.git\/refs\/heads\/(.+)\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(refs\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(heads\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?((.+)\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(\.dockerignore\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(SalesOrderService\/Dockerfile\b|$)) added to the regex cache
[testcontainers.org 00:00:00.27] Pattern ^([\\\/]?(Dockerfile\b|$)) added to the regex cache

Additional information

No response

HofmeisterAn commented 4 months ago

Removing the 5 exception lines at the bottom resolves the issue, but that may not be a viable solution in all cases.

I do not understand why you do not want to exclude the entire Git from your .dockerignore. Can you please change !.git/refs/heads/** to !.git/refs/heads/ and try it again? According to the Glob Tool, this line matches everything, which is how the implementation works.

Hogedal commented 4 months ago

I do not understand why you do not want to exclude the entire Git from your .dockerignore.

I don't really have a specific need to do so, but that's part of the .dockerignore file that visual studio generates when you add docker support to a project. It took a fair bit of time to track down the cause of this error, in large part because we expected that the library aught to play nicely with the "default" .dockerignore.

Can you please change !.git/refs/heads/** to !.git/refs/heads/ and try it again? According to the [Glob Tool], this line matches everything, which is how the implementation works.

It does appear to work with just this change, thank you.

HofmeisterAn commented 4 months ago

I looked into it, and it is not difficult to reproduce. It appears that the globstar (**) at the end of the negate pattern (!.git/refs/heads/**) adds ^([\\\/]?((.+)\b|$)) to the regex cache (which matches everything) and overrides (negates) the previous configurations (1, 2).

[Theory]
[ClassData(typeof(DockerignoreTestData))]
public void Issue1119(IEnumerable<string> patterns)
{
    // Given
    const string file = "src/.vs/Andel.Energi.GSOF/v17/TestStore/0/002.testlog";

    var ignoreFile = new IgnoreFile(patterns, NullLogger.Instance);

    // When
    var denies = ignoreFile.Denies(file);

    // Then
    Assert.True(denies);
}

private sealed class DockerignoreTestData : TheoryData<IEnumerable<string>>
{
    public DockerignoreTestData()
    {
        Add(["**/.vs"]);
        Add(["**/.vs", "!.git/refs/heads/**"]);
    }
}
HofmeisterAn commented 4 months ago

I believe I have found the root cause of the issue. It appears we split paths (directory separators) too eager and add too many regular expressions to the cache. Thanks for shareing. I need to do a few more tests, but I think I can create a PR soon.