Skip to content

NET SDK tool install repetition fails if folder was copied via Docker on Windows #42182

Open
@hotchkj

Description

@hotchkj

Describe the bug

In theory, installing NET tools post-8.0.200 should repeat reliably with Tool 'my.tool' was reinstalled with the stable version....

If you do this across a Windows Docker context, the NET tool install fails if repeated. This appears to suggest the NET tool install code is not properly checking for some filesystem state, as 'normal' file operations on that folder work correctly, and in theory Docker has simply copied the contents across from Windows filesystem A to B.

Thanks in advance for your attention.

To Reproduce

  • Install Cake.Tool to a custom tool install folder e.g. .\cake
  • Create a Docker image from a base Windows image that does the following:
    • Installs the NET SDK from CI-CD powershell
    • Copies the current folder (and thus the installed tool in cake subfolder) to a folder in the Docker image
  • Run the Docker image as a container
  • Attempt to reinstall the tool at the copied location inside the container multiple times

Expected results:

Tool consistently reinstalls correctly.

Actual results:

Tool install fails with exception detailed below.

Workaround:

Excluding the toolpath folder from the Docker context i.e. in the below example a .dockerignore containing cake/ fixes the issue, demonstrating the issue is with the presence of the folder and how the NET tool installer tries to alter it. Of course, this means the tool has to be re-downloaded and re-installed, wasting time.

Exceptions (if any)

Unhandled exception: System.IO.IOException: Cannot create 'C:\BUILD\cake\.store\cake.tool\4.0.0' because a file or directory with the same name already exists.
   at System.IO.FileSystem.MoveDirectory(String sourceFullPath, String destFullPath, Boolean _)
   at System.IO.FileSystem.MoveDirectory(String sourceFullPath, String destFullPath)
   at Microsoft.DotNet.Cli.Utils.FileAccessRetrier.RetryOnMoveAccessFailure(Action action)
   at Microsoft.DotNet.Cli.TransactionalAction.EnlistmentNotification.Rollback(Enlistment enlistment)
   at System.Transactions.VolatileEnlistmentAborting.EnterState(InternalEnlistment enlistment)
   at System.Transactions.TransactionStateAborted.EnterState(InternalTransaction tx)
   at System.Transactions.Transaction.Rollback()
   at System.Transactions.TransactionScope.InternalDispose()
   at System.Transactions.TransactionScope.Dispose()
   at Microsoft.DotNet.Cli.TransactionalAction.Run[T](Func`1 action, Action commit, Action rollback)
   at Microsoft.DotNet.Cli.TransactionalAction.Run(Action action, Action commit, Action rollback)
   at Microsoft.DotNet.Tools.Tool.Install.ToolInstallGlobalOrToolPathCommand.RunWithHandlingUninstallError(Action uninstallAction)
   at Microsoft.DotNet.Tools.Tool.Install.ToolInstallGlobalOrToolPathCommand.Execute()
   at System.CommandLine.Invocation.InvocationPipeline.Invoke(ParseResult parseResult)
   at Microsoft.DotNet.Cli.Program.ProcessArgs(String[] args, TimeSpan startupTime, ITelemetry telemetryClient)

Further technical details

Detailed command line for reproduction, with indenting for readability:

Local environment:

$SdkInstallDir = "./dotnet-sdk"
Invoke-WebRequest -Uri "https://raw.githubusercontent.com/dotnet/install-scripts/main/src/dotnet-install.ps1" -OutFile "$SdkInstallDir/dotnet-install.ps1"
& $SdkInstallDir/dotnet-install.ps1 -JsonFile 'global.json' -InstallDir $SdkInstallDir
$env:DOTNET_ROOT = $SdkInstallDir

dotnet --list-sdks
    8.0.202 [D:\repro\dotnet-sdk\sdk]
    8.0.303 [D:\repro\dotnet-sdk\sdk]

docker version
    Client: Mirantis Container Runtime
     Version:           20.10.9
     API version:       1.41
     Go version:        go1.16.12m2
     Git commit:        591094d
     Built:             12/21/2021 21:34:30
     OS/Arch:           windows/amd64
     Context:           default
     Experimental:      true

    Server: Mirantis Container Runtime
     Engine:
      Version:          20.10.9
      API version:      1.41 (minimum version 1.24)
      Go version:       go1.16.12m2
      Git commit:       9b96ce992b
      Built:            12/21/2021 21:33:06
      OS/Arch:          windows/amd64
      Experimental:     false

dotnet --info
    .NET SDK:
     Version:           8.0.303
     Commit:            29ab8e3268
     Workload version:  8.0.300-manifests.4e94be9c
     MSBuild version:   17.10.4+10fbfbf2e

    Runtime Environment:
     OS Name:     Windows
     OS Version:  10.0.20348
     OS Platform: Windows
     RID:         win-x64
     Base Path:   D:\repro\dotnet-sdk\sdk\8.0.303\

    .NET workloads installed:
    There are no installed workloads to display.

    Host:
      Version:      8.0.7
      Architecture: x64
      Commit:       2aade6beb0

    .NET SDKs installed:
      8.0.202 [D:\repro\dotnet-sdk\sdk]
      8.0.303 [D:\repro\dotnet-sdk\sdk]

    .NET runtimes installed:
      Microsoft.AspNetCore.App 8.0.3 [D:\repro\dotnet-sdk\shared\Microsoft.AspNetCore.App]
      Microsoft.AspNetCore.App 8.0.7 [D:\repro\dotnet-sdk\shared\Microsoft.AspNetCore.App]
      Microsoft.NETCore.App 8.0.3 [D:\repro\dotnet-sdk\shared\Microsoft.NETCore.App]
      Microsoft.NETCore.App 8.0.7 [D:\repro\dotnet-sdk\shared\Microsoft.NETCore.App]
      Microsoft.WindowsDesktop.App 8.0.3 [D:\repro\dotnet-sdk\shared\Microsoft.WindowsDesktop.App]
      Microsoft.WindowsDesktop.App 8.0.7 [D:\repro\dotnet-sdk\shared\Microsoft.WindowsDesktop.App]

    Other architectures found:
      None

    Environment variables:
      DOTNET_ROOT       [D:\repro\dotnet-sdk]

    global.json file:
      D:\repro\global.json

    Learn more:
      https://aka.ms/dotnet/info

    Download .NET:
      https://aka.ms/dotnet/download

$cakeDir = Join-Path $PSScriptRoot "cake"
dotnet tool install Cake.Tool --tool-path $cakeDir --version 4.0.0

    Tool 'cake.tool' was reinstalled with the stable version (version '4.0.0').

& docker.exe build --file RecipeWindowsDockerfileCode --tag "sdk_testing_code:8.0.303" .

    Sending build context to Docker daemon  155.9MB
    Step 1/7 : FROM mcr.microsoft.com/dotnet/framework/runtime:4.8.1-20240514-windowsservercore-ltsc2022
     ---> f2814b275efe
    Step 2/7 : ENV DOT_NET_VERSION=${DOT_NET_VERSION}       DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1      DOTNET_CLI_TELEMETRY_OPTOUT=1            DOTNET_NOLOGO=1                          DOTNET_ROOT=C:\\Dotnet\\x64
     ---> Running in b56f3298722a
    Removing intermediate container b56f3298722a
     ---> 09ef8892ea83
    Step 3/7 : ADD https://raw.githubusercontent.com/dotnet/install-scripts/main/src/dotnet-install.ps1 C:\\TEMP\\dotnet-install.ps1
    Downloading  54.69kB
     ---> d7b611251bd4
    Step 4/7 : RUN setx /m PATH "C:\Dotnet\x64;%PATH%" &&     powershell -File C:\\TEMP\\dotnet-install.ps1 -Channel LTS -Version 8.0.303 -InstallDir C:\\Dotnet\\x64 -Architecture x64 -NoPath
     ---> Running in 136070dc18e6

    SUCCESS: Specified value was saved.
    dotnet-install: Downloaded file https://dotnetcli.azureedge.net/dotnet/Sdk/8.0.303/dotnet-sdk-8.0.303-win-x64.zip size is 291718357 bytes.
    dotnet-install: Either downloaded or local package size can not be measured. One of them may be corrupted.
    dotnet-install: Extracting the archive.
    dotnet-install: Binaries of dotnet can be found in C:\Dotnet\x64\
    dotnet-install: Note that the script does not resolve dependencies during installation.
    dotnet-install: To check the list of dependencies, go to https://learn.microsoft.com/dotnet/core/install/windows#dependencies
    dotnet-install: Installed version is 8.0.303
    dotnet-install: Installation finished
    Removing intermediate container 136070dc18e6
     ---> 3d3be5bff89a
    Step 5/7 : RUN mkdir C:\\BUILD
     ---> Running in 2bd0c97695e5
    Removing intermediate container 2bd0c97695e5
     ---> d53645c7a679
    Step 6/7 : WORKDIR C:/BUILD
     ---> Running in 392f23ba8e2f
    Removing intermediate container 392f23ba8e2f
     ---> af7420fc7fcb
    Step 7/7 : COPY / C:/BUILD/
     ---> 5eb5fd19d95a
    Successfully built 5eb5fd19d95a
    Successfully tagged sdk_testing_code:8.0.303

& docker.exe run --rm sdk_testing_code:8.0.303 powershell -File C:\BUILD\main.ps1

Docker environment:

dotnet --info
    .NET SDK:
     Version:           8.0.303
     Commit:            29ab8e3268
     Workload version:  8.0.300-manifests.c915c39d
     MSBuild version:   17.10.4+10fbfbf2e

    Runtime Environment:
     OS Name:     Windows
     OS Version:  10.0.20348
     OS Platform: Windows
     RID:         win-x64
     Base Path:   C:\Dotnet\x64\sdk\8.0.303\

    .NET workloads installed:
    There are no installed workloads to display.

    Host:
      Version:      8.0.7
      Architecture: x64
      Commit:       2aade6beb0

    .NET SDKs installed:
      8.0.303 [C:\Dotnet\x64\sdk]

    .NET runtimes installed:
      Microsoft.AspNetCore.App 8.0.7 [C:\Dotnet\x64\shared\Microsoft.AspNetCore.App]
      Microsoft.NETCore.App 8.0.7 [C:\Dotnet\x64\shared\Microsoft.NETCore.App]
      Microsoft.WindowsDesktop.App 8.0.7 [C:\Dotnet\x64\shared\Microsoft.WindowsDesktop.App]

    Other architectures found:
      None

    Environment variables:
      DOTNET_ROOT       [C:\Dotnet\x64]

    global.json file:
      C:\BUILD\global.json

    Learn more:
      https://aka.ms/dotnet/info

    Download .NET:
      https://aka.ms/dotnet/download

& dotnet tool install Cake.Tool --tool-path $cakeDir --version 4.0.0

    Tool 'cake.tool' was reinstalled with the stable version (version '4.0.0').

& dotnet tool install Cake.Tool --tool-path $cakeDir --version 4.0.0

    Unhandled exception: System.IO.IOException: Cannot create 'C:\BUILD\cake\.store\cake.tool\4.0.0' because a file or directory with the same name already exists.
       at System.IO.FileSystem.MoveDirectory(String sourceFullPath, String destFullPath, Boolean _)
       at System.IO.FileSystem.MoveDirectory(String sourceFullPath, String destFullPath)
       at Microsoft.DotNet.Cli.Utils.FileAccessRetrier.RetryOnMoveAccessFailure(Action action)
       at Microsoft.DotNet.Cli.TransactionalAction.EnlistmentNotification.Rollback(Enlistment enlistment)
       at System.Transactions.VolatileEnlistmentAborting.EnterState(InternalEnlistment enlistment)
       at System.Transactions.TransactionStateAborted.EnterState(InternalTransaction tx)
       at System.Transactions.Transaction.Rollback()
       at System.Transactions.TransactionScope.InternalDispose()
       at System.Transactions.TransactionScope.Dispose()
       at Microsoft.DotNet.Cli.TransactionalAction.Run[T](Func`1 action, Action commit, Action rollback)
       at Microsoft.DotNet.Cli.TransactionalAction.Run(Action action, Action commit, Action rollback)
       at Microsoft.DotNet.Tools.Tool.Install.ToolInstallGlobalOrToolPathCommand.RunWithHandlingUninstallError(Action uninstallAction)
       at Microsoft.DotNet.Tools.Tool.Install.ToolInstallGlobalOrToolPathCommand.Execute()
       at System.CommandLine.Invocation.InvocationPipeline.Invoke(ParseResult parseResult)
       at Microsoft.DotNet.Cli.Program.ProcessArgs(String[] args, TimeSpan startupTime, ITelemetry telemetryClient)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions