Skip to content

Commit

Permalink
.NET Core runtime startup script: Check if the startup executable fil…
Browse files Browse the repository at this point in the history
…e is meant for Linux (#146)
  • Loading branch information
kichalla authored May 17, 2019
1 parent 2e3a42e commit b878c1a
Show file tree
Hide file tree
Showing 13 changed files with 3,562 additions and 60 deletions.
5 changes: 5 additions & 0 deletions images/runtime/dotnetcore/Dockerfile.template
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ RUN ./build.sh dotnetcore /opt/startupcmdgen/startupcmdgen

FROM %DOTNETCORE_BASE_IMAGE%

RUN apt-get update \
&& apt-get install -y \
file \
&& rm -rf /var/lib/apt/lists/*

# Bake Application Insights key from pipeline variable into final image
ARG AI_KEY
ENV ORYX_AI_INSTRUMENTATION_KEY=${AI_KEY}
Expand Down
7 changes: 6 additions & 1 deletion images/runtime/dotnetcore/DockerfileWithCurlUpdate.template
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ FROM %DOTNETCORE_BASE_IMAGE%
# We manually update it here so we can still depend on the original images.
# This command should be removed once support for deprecated .NET core images is halted.
RUN sed -i '/jessie-updates/d' /etc/apt/sources.list # Now archived
RUN apt-get update && apt-get install curl --yes

RUN apt-get update \
&& apt-get install -y \
curl \
file \
&& rm -rf /var/lib/apt/lists/*

# Bake Application Insights key from pipeline variable into final image
ARG AI_KEY
Expand Down
2 changes: 1 addition & 1 deletion src/startupscriptgenerator/dotnetcore/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func main() {
}

scriptBuilder := strings.Builder{}
scriptBuilder.WriteString("#!/bin/sh\n")
scriptBuilder.WriteString("#!/bin/bash\n")
scriptBuilder.WriteString("set -e\n\n")

if fullRunFromPath != "" {
Expand Down
138 changes: 83 additions & 55 deletions src/startupscriptgenerator/dotnetcore/scriptgenerator.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,62 +41,90 @@ func (gen *DotnetCoreStartupScriptGenerator) GenerateEntrypointScript(scriptBuil
scriptBuilder.WriteString("readonly defaultAppFileDir=\"" + defaultAppFileDir + "\"\n")
scriptBuilder.WriteString("readonly defaultAppFilePath=\"" + gen.DefaultAppFilePath + "\"\n")

scriptBuilder.WriteString("cd \"$appPath\"\n")
scriptBuilder.WriteString("if [ ! -z \"$userStartUpCommand\" ]; then\n")
scriptBuilder.WriteString(" len=${#userStartUpCommand[@]}\n")
scriptBuilder.WriteString(" if [ \"$len\" -eq \"1\" ]; then\n")
scriptBuilder.WriteString(" if [ -f \"${userStartUpCommand[$0]}\" ]; then\n")
scriptBuilder.WriteString(" startUpCommand=\"$userStartUpCommand\"\n")
scriptBuilder.WriteString(" else\n")
scriptBuilder.WriteString(" echo \"Could not find the startup file '${userStartUpCommand[$0]}' on disk.\"\n")
scriptBuilder.WriteString(" fi\n")
scriptBuilder.WriteString(" elif [ \"$len\" -eq \"2\" ] && [ \"${userStartUpCommand[$0]}\" == \"dotnet\" ]; then\n")
scriptBuilder.WriteString(" if [ -f \"${userStartUpCommand[$1]}\" ]; then\n")
scriptBuilder.WriteString(" startUpCommand=\"$userStartUpCommand\"\n")
scriptBuilder.WriteString(" else\n")
scriptBuilder.WriteString(" echo \"Could not find the file '${userStartUpCommand[$1]}' on disk.\"\n")
scriptBuilder.WriteString(" fi\n")
scriptBuilder.WriteString(" else\n")
scriptBuilder.WriteString(" startUpCommand=\"$userStartUpCommand\"\n")
scriptBuilder.WriteString(" fi\n")
scriptBuilder.WriteString("fi\n\n")

scriptBuilder.WriteString("if [ -z \"$startUpCommand\" ]; then\n")
scriptBuilder.WriteString(" echo Finding the startup file name...\n")
scriptBuilder.WriteString(" for file in *; do \n")
scriptBuilder.WriteString(" if [ -f \"$file\" ]; then \n")
scriptBuilder.WriteString(" case $file in\n")
scriptBuilder.WriteString(" *.runtimeconfig.json)\n")
scriptBuilder.WriteString(" startupDllFileNamePrefix=${file%%.runtimeconfig.json}\n")
scriptBuilder.WriteString(" startupExecutableFileName=\"$startupDllFileNamePrefix\"\n")
scriptBuilder.WriteString(" startupDllFileName=\"$startupDllFileNamePrefix.dll\"\n")
scriptBuilder.WriteString(" break\n")
scriptBuilder.WriteString(" ;;\n")
scriptBuilder.WriteString(" esac\n")
scriptBuilder.WriteString(" fi\n")
scriptBuilder.WriteString(" done\n\n")
scriptBuilder.WriteString(" if [ -f \"$startupExecutableFileName\" ]; then\n")
scriptBuilder.WriteString(" echo \"Found the startup file '$startupExecutableFileName'\"\n")
scriptBuilder.WriteString(" startUpCommand=\"./$startupExecutableFileName\"\n")
scriptBuilder.WriteString(" elif [ -f \"$startupDllFileName\" ]; then\n")
scriptBuilder.WriteString(" echo \"Found the startup file '$startupDllFileName'\"\n")
scriptBuilder.WriteString(" startUpCommand=\"dotnet '$startupDllFileName'\"\n")
scriptBuilder.WriteString(" fi\n")
scriptBuilder.WriteString("fi\n\n")

scriptBuilder.WriteString("if [ -z \"$startUpCommand\" ]; then\n")
scriptBuilder.WriteString(" if [ -f \"$defaultAppFilePath\" ]; then\n")
scriptBuilder.WriteString(" cd \"$defaultAppFileDir\"\n")
scriptBuilder.WriteString(" startUpCommand=\"dotnet '$defaultAppFilePath'\"\n")
scriptBuilder.WriteString(" else\n")
scriptBuilder.WriteString(" echo Unable to start the application.\n")
scriptBuilder.WriteString(" exit 1\n")
scriptBuilder.WriteString(" fi\n\n")
scriptBuilder.WriteString("fi\n\n")

scriptBuilder.WriteString("echo \"Running the command '$startUpCommand'...\"\n")
scriptBuilder.WriteString("eval \"$startUpCommand\"\n\n")
script := `
isLinuxExecutable() {
local file="$1"
if [ -x "$file" ] && file "$file" | grep -q "GNU/Linux"
then
isLinuxExecutableResult="true"
else
isLinuxExecutableResult="false"
fi
}
cd "$appPath"
if [ ! -z "$userStartUpCommand" ]; then
len=${#userStartUpCommand[@]}
if [ "$len" -eq "1" ]; then
file="${userStartUpCommand[0]}"
if [ -f "$file" ]; then
# The startup command could be for example: 'todoApp' or './todoApp'
# So just extract the 'todoApp' part and prefix it with './' to be './todoApp'
startUpCommand="./${file##*/}"
else
echo "Could not find the startup file '$file' on disk."
fi
elif [ "$len" -eq "2" ] && [ "${userStartUpCommand[0]}" == "dotnet" ]; then
if [ -f "${userStartUpCommand[1]}" ]; then
startUpCommand="$userStartUpCommand"
else
echo "Could not find the file '${userStartUpCommand[1]}' on disk."
fi
else
startUpCommand="$userStartUpCommand"
fi
fi
if [ -z "$startUpCommand" ]; then
echo Finding the startup file name...
for file in *; do
if [ -f "$file" ]; then
case $file in
*.runtimeconfig.json)
startupDllFileNamePrefix=${file%%.runtimeconfig.json}
startupExecutableFileName="$startupDllFileNamePrefix"
startupDllFileName="$startupDllFileNamePrefix.dll"
break
;;
esac
fi
done
if [ -f "$startupExecutableFileName" ]; then
# Starting ASP.NET Core 3.0, an executable is created based on the platform where it is published from,
# so for example, if a user does a publish (not self-contained) on Mac, there would be files like 'todoApp'
# and 'todoApp.dll'. In this scenario the 'todoApp' executable is actually meant for Mac and not for Linux.
# So here we check for the file type and fall back to using 'dotnet todoApp.dll'.
isLinuxExecutable $startupExecutableFileName
if [ "$isLinuxExecutableResult" == "true" ]; then
echo "Found the startup executable file '$startupExecutableFileName'"
startUpCommand="./$startupExecutableFileName"
else
echo "Cannot use executable '$startupExecutableFileName' as startup file as it is not meant for Linux"
fi
fi
if [ -z "$startUpCommand" ] && [ -f "$startupDllFileName" ]; then
echo "Found the startup file '$startupDllFileName'"
startUpCommand="dotnet '$startupDllFileName'"
fi
fi
if [ -z "$startUpCommand" ]; then
if [ -f "$defaultAppFilePath" ]; then
cd "$defaultAppFileDir"
startUpCommand="dotnet '$defaultAppFilePath'"
else
echo Unable to start the application.
exit 1
fi
fi
echo "Running the command '$startUpCommand'..."
eval "$startUpCommand"
`
scriptBuilder.WriteString(script)
var runScript = scriptBuilder.String()
logger.LogInformation("Run script content:\n" + runScript)
return runScript
Expand Down
45 changes: 42 additions & 3 deletions tests/Oryx.Integration.Tests/DotNetCoreEndToEndTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// Licensed under the MIT license.
// --------------------------------------------------------------------------------------------

using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Oryx.BuildScriptGenerator.DotNetCore;
Expand Down Expand Up @@ -483,7 +484,7 @@ await EndToEndTestHelper.BuildRunAndAssertAppAsync(
}

[Fact]
public async Task CanRun_SelfContainedApp()
public async Task CanRun_SelfContainedApp_TargetedForLinux()
{
// ****************************************************************
// A self-contained app is an app which does not depend on whether a .NET Core runtime is present on the
Expand Down Expand Up @@ -539,7 +540,45 @@ await EndToEndTestHelper.BuildRunAndAssertAppAsync(
}

[Fact]
public async Task CanBuildAndRun_NetCore30WebApp_UsingExplicitStartupCommand()
public async Task CanRun_NetCore30App_PublishedOnMacMachine_ButRunOnNetCore30RuntimeContainer()
{
// This test verifies that we fallback to using 'dotnet TodoAppFromMac.dll' since the executable
// file 'TodoAppFromMac' was indeed generated from a Mac OS and cannot be run in a Linux container.

// Arrange
var hostDir = Path.Combine(_hostSamplesDir, "DotNetCore", "TodoAppFromMac");
var volume = DockerVolume.Create(hostDir);
var appDir = volume.ContainerDir;
var runtimeImageScript = new ShellScriptBuilder()
.AddCommand($"oryx -appPath {appDir} -bindPort {ContainerPort}")
.AddCommand(DefaultStartupFilePath)
.ToString();

await EndToEndTestHelper.RunAndAssertAppAsync(
"oryxdevms/dotnetcore-3.0",
_output,
new List<DockerVolume>() { volume },
environmentVariables: null,
ContainerPort,
link: null,
"/bin/sh",
new[]
{
"-c",
runtimeImageScript
},
async (hostPort) =>
{
var data = await _httpClient.GetStringAsync($"http://localhost:{hostPort}/");
Assert.Contains("Hello World!", data);
},
new DockerCli());
}

[Theory]
[InlineData("NetCoreApp30.WebApp")]
[InlineData("./NetCoreApp30.WebApp")]
public async Task CanBuildAndRun_NetCore30WebApp_UsingExplicitStartupCommand(string startupCommand)
{
// Arrange
var dotnetcoreVersion = "3.0";
Expand All @@ -552,7 +591,7 @@ public async Task CanBuildAndRun_NetCore30WebApp_UsingExplicitStartupCommand()
.ToString();
var runtimeImageScript = new ShellScriptBuilder()
.AddCommand(
$"oryx -appPath {appOutputDir} -userStartupCommand ./NetCoreApp30.WebApp -bindPort {ContainerPort}")
$"oryx -appPath {appOutputDir} -userStartupCommand {startupCommand} -bindPort {ContainerPort}")
.AddCommand(DefaultStartupFilePath)
.ToString();

Expand Down
3 changes: 3 additions & 0 deletions tests/SampleApps/DotNetCore/TodoAppFromMac/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
This is a regularly(not self-contained) published ASP.NET Core 3.0 app on a Mac machine.
This app is used to test the scenario where we fall back to executing the app using 'dotnet TodoAppFromMac.dll'
rather than the executable 'TodoAppFromMac' because that executable is for Mac and not Linux.
Binary file not shown.
Loading

0 comments on commit b878c1a

Please sign in to comment.