dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.16k stars 4.72k forks source link

SpecialFolder.ApplicationData is blank inside of an Alpine docker container #69449

Closed rcdailey closed 2 years ago

rcdailey commented 2 years ago

Discussed in https://github.com/dotnet/runtime/discussions/69362

Originally posted by **rcdailey** May 14, 2022 I'm doing this in a .NET 6.0 C# console CLI application: ```cs Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); ``` This returns a blank string when I run inside of a docker container created with `FROM alpine`. Specifically: ```dockerfile FROM alpine ENV HOME=/config ENV DOTNET_BUNDLE_EXTRACT_BASE_DIR=/tmp/.net ENV CONFIG_FILE="/config/recyclarr.yml" ENV CRON_SCHEDULE_SONARR="@daily" ENV CRON_SCHEDULE_RADARR="@daily" RUN apk add --no-cache libstdc++ icu COPY --chmod=544 --from=build /build/recyclarr /usr/local/bin COPY --chmod=544 entrypoint.sh / VOLUME /config ENTRYPOINT ["/entrypoint.sh"] ``` I build my executable using this command: ```pwsh dotnet publish src\Recyclarr ` --output publish\linux-musl-x64 ` --configuration Release ` --runtime linux-musl-x64 ` --self-contained true ``` Relevant msbuild settings: ```xml true true true true true ``` I verified in `entrypoint.sh` that `$HOME` is set and returns the expected value. I'm not sure if this is an issue on my side or if .NET just doesn't support this environment. I have spent all day googling but I've had no luck. Can anyone help me figure out how to get this working?
ghost commented 2 years ago

Tagging subscribers to this area: @agocke, @vitek-karas, @vsadov See info in area-owners.md if you want to be subscribed.

Issue Details
### Discussed in https://github.com/dotnet/runtime/discussions/69362
Originally posted by **rcdailey** May 14, 2022 I'm doing this in a .NET 6.0 C# console CLI application: ```cs Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); ``` This returns a blank string when I run inside of a docker container created with `FROM alpine`. Specifically: ```dockerfile FROM alpine ENV HOME=/config ENV DOTNET_BUNDLE_EXTRACT_BASE_DIR=/tmp/.net ENV CONFIG_FILE="/config/recyclarr.yml" ENV CRON_SCHEDULE_SONARR="@daily" ENV CRON_SCHEDULE_RADARR="@daily" RUN apk add --no-cache libstdc++ icu COPY --chmod=544 --from=build /build/recyclarr /usr/local/bin COPY --chmod=544 entrypoint.sh / VOLUME /config ENTRYPOINT ["/entrypoint.sh"] ``` I build my executable using this command: ```pwsh dotnet publish src\Recyclarr ` --output publish\linux-musl-x64 ` --configuration Release ` --runtime linux-musl-x64 ` --self-contained true ``` Relevant msbuild settings: ```xml true true true true true ``` I verified in `entrypoint.sh` that `$HOME` is set and returns the expected value. I'm not sure if this is an issue on my side or if .NET just doesn't support this environment. I have spent all day googling but I've had no luck. Can anyone help me figure out how to get this working?
Author: rcdailey
Assignees: -
Labels: `area-Single-File`, `untriaged`
Milestone: -
rcdailey commented 2 years ago

This is the request for this bug (copied from the discussion):

I think that, if possible, dotnet runtime should do more to abstract these nitty-gritty details away from me. For example I find it weird that even though $HOME is set, $XDG_CONFIG_HOME is still required, According to the XDG spec page I read, it should fall back to $HOME. I'm not sure if that's a linux responsibility or an application one. But I don't think it's unreasonable for dotnet runtime to use $HOME (which I explicitly set, even before I started this discussion thread).

For the record, the specification I read states:

$XDG_CONFIG_HOME defines the base directory relative to which user-specific configuration files should be stored. If $XDG_CONFIG_HOME is either not set or empty, a default equal to $HOME/.config should be used.

This is what the dotnet runtime should be doing, IMHO.

rcdailey commented 2 years ago

More broadly, even though this is specifically about XDG_CONFIG_HOME, I think for .NET 7.0, the entire XDG specification should be implemented and respected equally on all Linux platforms (Alpine, unRAID, Debian, etc).

Ultimately I want to be confident that "Linux" support, broadly speaking, is simple and consistently supported for my .NET application. If I'm getting different behavior on different distros and having to, due to trial & error, set various arguably implementation-detail environment variables to get my app to behave consistently, that to me is a fatal design flaw.

I sadly am not sure how easy or hard what I'm asking for is. So the best I can offer is my opinion in broad brush strokes.

ghost commented 2 years ago

Tagging subscribers to this area: @dotnet/area-system-io See info in area-owners.md if you want to be subscribed.

Issue Details
### Discussed in https://github.com/dotnet/runtime/discussions/69362
Originally posted by **rcdailey** May 14, 2022 I'm doing this in a .NET 6.0 C# console CLI application: ```cs Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); ``` This returns a blank string when I run inside of a docker container created with `FROM alpine`. Specifically: ```dockerfile FROM alpine ENV HOME=/config ENV DOTNET_BUNDLE_EXTRACT_BASE_DIR=/tmp/.net ENV CONFIG_FILE="/config/recyclarr.yml" ENV CRON_SCHEDULE_SONARR="@daily" ENV CRON_SCHEDULE_RADARR="@daily" RUN apk add --no-cache libstdc++ icu COPY --chmod=544 --from=build /build/recyclarr /usr/local/bin COPY --chmod=544 entrypoint.sh / VOLUME /config ENTRYPOINT ["/entrypoint.sh"] ``` I build my executable using this command: ```pwsh dotnet publish src\Recyclarr ` --output publish\linux-musl-x64 ` --configuration Release ` --runtime linux-musl-x64 ` --self-contained true ``` Relevant msbuild settings: ```xml true true true true true ``` I verified in `entrypoint.sh` that `$HOME` is set and returns the expected value. I'm not sure if this is an issue on my side or if .NET just doesn't support this environment. I have spent all day googling but I've had no luck. Can anyone help me figure out how to get this working?
Author: rcdailey
Assignees: -
Labels: `area-System.IO`, `area-Single-File`
Milestone: 7.0.0
agocke commented 2 years ago

Not quite sure where to triage this one -- it's about System.Environment.SpecialFolder, so I put it in System.IO.

wzchua commented 2 years ago

See https://github.com/dotnet/runtime/issues/63214

tmds commented 2 years ago

ApplicationData follows the XDG spec.

GetFolderPath returns an empty string when the folder doesn't exist, which is probably what is happening here. You can use the overload with SpecialFolderOption.DoNotVerify to skip the exists check. Or with SpecialFolderOption.Create to create it when it doesn't exist.

rcdailey commented 2 years ago

I tested creating the directory as well, it still returned a blank string. Again, with $HOME set, and $HOME/.config created: I get a blank string. Only worked when I explicitly set the XDG variable as explained previously.

tmds commented 2 years ago

I cannot reproduce that.

I run these commands to install dotnet in the alpine image:

cd
apk add bash icu-libs krb5-libs libgcc libintl libssl1.1 libstdc++ zlib
export DOTNET_ROOT=$(pwd)/.dotnet
wget https://download.visualstudio.microsoft.com/download/pr/108663ba-7326-432d-97c6-d3925e9990cc/dd9876b6a0fc0cdae66006747cb3dda0/dotnet-sdk-6.0.300-linux-musl-x64.tar.gz
DOTNET_FILE=dotnet-sdk-6.0.300-linux-musl-x64.tar.gz
mkdir -p "$DOTNET_ROOT" && tar zxf "$DOTNET_FILE" -C "$DOTNET_ROOT"
export PATH=$PATH:$DOTNET_ROOT

Then create a .NET app to print ApplicationData:

~ # dotnet new console -o console

Welcome to .NET 6.0!
---------------------
SDK Version: 6.0.300

Telemetry
---------
The .NET tools collect usage data in order to help us improve your experience. It is collected by Microsoft and shared with the community. You can opt-out of telemetry by setting the DOTNET_CLI_TELEMETRY_OPTOUT environment variable to '1' or 'true' using your favorite shell.

Read more about .NET CLI Tools telemetry: https://aka.ms/dotnet-cli-telemetry

----------------
Installed an ASP.NET Core HTTPS development certificate.
To trust the certificate run 'dotnet dev-certs https --trust' (Windows and macOS only).
Learn about HTTPS: https://aka.ms/dotnet-https
----------------
Write your first app: https://aka.ms/dotnet-hello-world
Find out what's new: https://aka.ms/dotnet-whats-new
Explore documentation: https://aka.ms/dotnet-docs
Report issues and find source on GitHub: https://github.com/dotnet/core
Use 'dotnet --help' to see available commands or visit: https://aka.ms/dotnet-cli
--------------------------------------------------------------------------------------
The template "Console App" was created successfully.

Processing post-creation actions...
Running 'dotnet restore' on /root/console/console.csproj...
  Determining projects to restore...
  Restored /root/console/console.csproj (in 121 ms).
Restore succeeded.

~ # cd console/
~/console # cat >Program.cs <<EOF
> Console.WriteLine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData, Environment.SpecialFolderOption.DoNotVerify));
> EOF
~/console # dotnet run
/root/.config

This prints the expected value.

In the container, XDG_CONFIG_HOME is not set, and the ~/.config dir doesn't exist:

~/console # stat /root/.config
stat: can't stat '/root/.config': No such file or directory
~/console # echo $XDG_CONFIG_HOME

If I leave out Environment.SpecialFolderOption.DoNotVerify, it does the same thing as your reproducer: print an empty line.

rcdailey commented 2 years ago

Your use case isn't exactly the same as mine (although I'm not certain the differences matter):

Does it still work for you?

tmds commented 2 years ago

Does it still work for you?

Yes, this works fine.

$ ./console
/home/tmds/.config
$ HOME=/tmp/foo ./console
/tmp/foo/.config
$ mkdir -p /tmp/foo/.config
$ HOME=/tmp/foo ./console
/tmp/foo/.config

with Program.cs:

Console.WriteLine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData, Environment.SpecialFolderOption.DoNotVerify));
rcdailey commented 2 years ago

Ok I played with this a bunch more and I must have done something weird earlier. I apologize for the mess; this is a tough problem to wrap my head around so I probably messed up somewhere.

When I just define HOME and explicitly create $HOME/.config, it's working now. I could have sworn I did this before and it wasn't working. So maybe I broke my own use case somewhere along the line.

So I think ultimately this comes down to what you were saying: That the directories have to exist or else it returns a blank string. I think that the directory should be created automatically if it does not exist. I'm not sure if this is the responsibility of .NET, though, since that gets into OS environment issues too.

Thank you for taking the time to reproduce this with me.

Clockwork-Muse commented 2 years ago

I think that the directory should be created automatically if it does not exist.

ApplicationData follows the XDG spec.

GetFolderPath returns an empty string when the folder doesn't exist, which is probably what is happening here. You can use the overload with SpecialFolderOption.DoNotVerify to skip the exists check. Or with SpecialFolderOption.Create to create it when it doesn't exist.

rcdailey commented 2 years ago

I forgot you said this before. Thank you for the reminder. I'll close this issue. Thanks again to everyone that helped.