diff --git a/10.0/aspnet/Dockerfile.rhel9 b/10.0/aspnet/Dockerfile.rhel9 index 2203320e4..4570c4216 100644 --- a/10.0/aspnet/Dockerfile.rhel9 +++ b/10.0/aspnet/Dockerfile.rhel9 @@ -21,6 +21,8 @@ LABEL name="ubi9/dotnet-100-aspnet" \ aspnet_version="$ASPNET_VERSION" USER 0 +# Don't clutter the app user's HOME +ENV HOME= # Install packages: # - aspnetcore-runtime-*: provides the ASP.NET Core shared framework. @@ -36,8 +38,7 @@ RUN [ "$IS_CI" == "true" ] || ( \ dotnet --list-runtimes | grep "Microsoft.NETCore.App ${DOTNET_VERSION} " && \ dotnet --list-runtimes | grep "Microsoft.AspNetCore.App ${ASPNET_VERSION} " ) -# Ensure we didn't add anything to HOME that is owned by root. -RUN chown -R $APP_UID:0 /opt/app-root && fix-permissions /opt/app-root - # Run container rootless. USER $APP_UID +# Set HOME for the random uid that runs the s2i app on OpenShift +ENV HOME=/opt/app-root diff --git a/10.0/build/Dockerfile.rhel9 b/10.0/build/Dockerfile.rhel9 index c59053355..5bbd23dc3 100644 --- a/10.0/build/Dockerfile.rhel9 +++ b/10.0/build/Dockerfile.rhel9 @@ -7,11 +7,24 @@ ARG DOTNET_SDK_VERSION=10.0 ARG IS_CI ARG DOTNET_TARBALL -ENV PATH=/opt/app-root/src/.local/bin:/opt/app-root/src/bin:/opt/app-root/.dotnet/tools:${PATH} \ +# Expose the port in case the SDK image is used as a base image through s2i. +EXPOSE 8080 + +ENV \ + # Add s2i bin dirs and .NET tools dir to PATH + PATH=/opt/app-root/.dotnet/tools:/opt/app-root/src/.local/bin:/opt/app-root/src/bin:/opt/app-root/bin:${PATH} \ + # S2I STI_SCRIPTS_PATH=/usr/libexec/s2i \ + DOTNET_APP_PATH=/opt/app-root/app \ + DOTNET_DEFAULT_CMD=default-cmd.sh \ + # Don't generate a developer certificate DOTNET_GENERATE_ASPNET_CERTIFICATE=false \ + # Don't download/extract docs for nuget packages + NUGET_XMLDOC_MODE=skip \ # This skips the first time running text DOTNET_NOLOGO=true \ + # Needed for the `dotnet watch` to detect changes in a container + DOTNET_USE_POLLING_FILE_WATCHER=true \ # Like Microsoft images, provide the SDK version DOTNET_SDK_VERSION=$DOTNET_SDK_VERSION @@ -31,10 +44,11 @@ LABEL name="ubi9/dotnet-100" \ # Switch to root for package installs USER 0 +# Don't let root clutter the app user's HOME +ENV HOME= -# Each language image can have 'contrib' a directory with extra files needed to -# run and build the applications. -COPY ./contrib/ /opt/app-root +# Helper scripts the user can call +COPY ./root/usr/bin /usr/bin # Copy the S2I scripts from the specific language image to $STI_SCRIPTS_PATH. COPY ./s2i/bin/ /usr/libexec/s2i @@ -42,15 +56,16 @@ COPY ./s2i/bin/ /usr/libexec/s2i # Install packages: # - dotnet-sdk--*: provides the .NET SDK. # - procps-ng: provides 'pidof' which is needed by the 'odo' Devfile to find the running 'dotnet' process. +# - findutils: provides 'find' which is used by the 'fix-permissions' script. RUN [ -n "${DOTNET_TARBALL}" ] || ( \ - INSTALL_PKGS="dotnet-sdk-10.0 procps-ng" && \ + INSTALL_PKGS="dotnet-sdk-10.0 procps-ng findutils" && \ microdnf install -y --setopt=tsflags=nodocs --setopt=install_weak_deps=0 $INSTALL_PKGS && \ microdnf clean all -y && \ # yum cache files may still exist (and quite large in size) rm -rf /var/cache/yum/* ) # Tarball install (in the runtime base image) RUN [ -z "${DOTNET_TARBALL}" ] || ( \ - INSTALL_PKGS="procps-ng" && \ + INSTALL_PKGS="procps-ng findutils" && \ microdnf install -y --setopt=tsflags=nodocs --setopt=install_weak_deps=0 $INSTALL_PKGS && \ microdnf clean all -y && \ # yum cache files may still exist (and quite large in size) @@ -64,24 +79,22 @@ RUN [ "$IS_CI" == "true" ] || ( \ printf "Checking SDK version ${DOTNET_SDK_VERSION} against:\n$(dotnet --list-sdks)" && \ dotnet --list-sdks | grep "^${DOTNET_SDK_VERSION} " ) -# Directory with the sources is set as the working directory. -RUN mkdir /opt/app-root/src -WORKDIR /opt/app-root/src - # Build the container tool. RUN /usr/libexec/s2i/container-tool build-tool -# Since $HOME is set to /opt/app-root, the yum install may have created config -# directories (such as ~/.pki/nssdb) there. These will be owned by root and can -# cause actions that work on all of /opt/app-root to fail. So we need to fix -# the permissions on those too. -RUN chown -R $APP_UID:0 /opt/app-root && fix-permissions /opt/app-root +# Run as the s2i assemble user +USER $APP_UID +# Set HOME for the random uid that runs the s2i app on OpenShift +ENV HOME=/opt/app-root -# Needed for the `dotnet watch` to detect changes in a container -ENV DOTNET_USE_POLLING_FILE_WATCHER=true +# Create s2i directories used for sources and published app +RUN mkdir -p /opt/app-root/src ${DOTNET_APP_PATH} -# Run container rootless. -USER $APP_UID +# Trigger first run to create .NET SDK dirs and config files under $HOME. +RUN dotnet help + +# Fix permissions on HOME for the random uid that runs the s2i app on OpenShift +RUN fix-permissions /opt/app-root -# Set the default CMD to start a shell. -CMD /bin/bash +# Use the s2i dir for sources as the working dir +WORKDIR /opt/app-root/src diff --git a/10.0/build/README.md b/10.0/build/README.md index e2f97c41c..e318c8ecc 100644 --- a/10.0/build/README.md +++ b/10.0/build/README.md @@ -184,3 +184,9 @@ an `.s2i/environment` file inside your source code repository. * **DOTNET_SDK_VERSION** This variable contains the version of the SDK. + +* **DOTNET_APP_PATH,DOTNET_DEFAULT_CMD** + + These variables are used by source-to-image. `DOTNET_APP_PATH` is the directory that gets published to by the s2i `assemble` script. + The s2i `run` script makes it the working directory and then starts the script named `$DOTNET_DEFAULT_CMD` (`default-cmd.sh`) in that directory. + That script is responsible for starting the .NET application. diff --git a/10.0/build/contrib/etc/trust_ssl_dirs b/10.0/build/contrib/etc/trust_ssl_dirs deleted file mode 100644 index 8b805e57c..000000000 --- a/10.0/build/contrib/etc/trust_ssl_dirs +++ /dev/null @@ -1,5 +0,0 @@ -if [ -n "$DOTNET_SSL_DIRS" ]; then - - echo "error: DOTNET_SSL_DIRS is no longer supported. Certificates can be trusted using SSL_CERT_DIR." - -fi diff --git a/10.0/runtime/root/usr/bin/fix-permissions b/10.0/build/root/usr/bin/fix-permissions similarity index 100% rename from 10.0/runtime/root/usr/bin/fix-permissions rename to 10.0/build/root/usr/bin/fix-permissions diff --git a/10.0/build/s2i/bin/assemble b/10.0/build/s2i/bin/assemble index 947e8d196..520fd3a8b 100755 --- a/10.0/build/s2i/bin/assemble +++ b/10.0/build/s2i/bin/assemble @@ -119,9 +119,6 @@ fi if [ "$BUILD_TYPE" == "source" ]; then echo "Using SDK: $(dotnet --version)" - # Print DOTNET_SSL_DIRS unsupported. - source /opt/app-root/etc/trust_ssl_dirs - # dotnet tools if [ -n "${DOTNET_TOOLS}" ]; then # Build nuget sources list for when doing the restore diff --git a/10.0/build/s2i/bin/run b/10.0/build/s2i/bin/run index 0f64db84e..94081cbb5 100755 --- a/10.0/build/s2i/bin/run +++ b/10.0/build/s2i/bin/run @@ -1,9 +1,6 @@ #!/bin/bash set -e -# Print DOTNET_SSL_DIRS unsupported. -source /opt/app-root/etc/trust_ssl_dirs - if [ -n "${DOTNET_INFO}" ]; then /usr/libexec/s2i/container-tool info fi diff --git a/10.0/build/test/run b/10.0/build/test/run index 0e231963e..497dac001 100755 --- a/10.0/build/test/run +++ b/10.0/build/test/run @@ -8,10 +8,6 @@ # short image name such as 'foobar', or it may be a fully qualified name in the # form of example.com/project/repository/image@sha256:hashinhex # -# ASPNET_IMAGE_NAME specifies the name of the runtime image used for split -# builds. If blank or unset, it is computed automatically as the base image -# used to build IMAGE_NAME. -# # OPENSHIFT_ONLY environment variable, if 'true', only tests features used by # OpenShift's try-it feature. # @@ -37,14 +33,6 @@ IMAGE_OS=${IMAGE_OS:-RHEL8} IMAGE_NAME=${IMAGE_NAME:-localhost/ubi9/dotnet-100} # Infer runtime image from SDK image when it is not explicitly set -if [[ ${ASPNET_IMAGE_NAME:-} == "" ]]; then - if [[ $(podman images -n --filter reference="${IMAGE_NAME}") == "" ]]; then - podman pull "${IMAGE_NAME}" - fi - base_image=$(podman image history --format json "${IMAGE_NAME}" | jq -r '[.[].comment | select (. != null )][0]' | cut -d' ' -f2) - echo "${base_image}" - ASPNET_IMAGE_NAME="${base_image}" -fi OPENSHIFT_ONLY=${OPENSHIFT_ONLY:-false} test_dir="$(readlink -f "$(dirname "${BASH_SOURCE[0]}")")" @@ -128,6 +116,11 @@ test_image() { # This is to ensure it is provided as environment variables. assert_contains $(docker_get_env $IMAGE_NAME DOTNET_SDK_VERSION) "$dotnet_version_series" + # DOTNET_DEFAULT_CMD + assert_equal $(docker_get_env $IMAGE_NAME DOTNET_DEFAULT_CMD) "default-cmd.sh" + # DOTNET_APP_PATH + assert_equal $(docker_get_env $IMAGE_NAME DOTNET_APP_PATH) "/opt/app-root/app" + # Verify $HOME != $CWD. See https://github.com/redhat-developer/s2i-dotnetcore/issues/28 local working_dir=$(docker_run ${IMAGE_NAME} pwd) assert_equal "${working_dir}" "/opt/app-root/src" @@ -149,6 +142,13 @@ test_image() { assert_contains "${certificate_stores}" "No such file or directory" } +test_port() { + test_start + + # Port 8080 is exposed (for s2i based apps) + assert_equal $(docker_get_exposedports $IMAGE_NAME) '{"8080/tcp":{}}' +} + test_consoleapp() { test_start @@ -410,54 +410,6 @@ test_config() { test_config_3 } -test_split_build() { - test_start - - # test openshift chained build - # see 'chaining builds' in the openshift docs - - # this app has DOTNET_PACK=true - local app=asp-net-hello-world-envvar - - local build_dir="${test_dir}/split_build" - rm -rf "$build_dir" - mkdir "$build_dir" - pushd "$build_dir" > /dev/null - - # extract app.tar.gz from build - local image=$(s2i_build ${app}) - local container=$(docker create ${image}) - docker cp ${container}:/opt/app-root/app.tar.gz app.tar.gz - # cleanup - docker_rm ${container} - docker_rmi ${image} - - # create image based on app.tar.gz - # we don't need to provide a CMD statement - # app.tar.gz has a default-cmd.sh which matches with the runtime CMD. - cat >Dockerfile < /dev/null -} - test_imagestream_sample() { test_start @@ -606,10 +558,10 @@ test_msbuild_exec_task() { } info "Testing ${IMAGE_NAME}" -info "Using runtime image ${ASPNET_IMAGE_NAME}" if [ ${OPENSHIFT_ONLY} != true ]; then test_image + test_port test_config test_usage test_default_cmd @@ -621,7 +573,6 @@ if [ ${OPENSHIFT_ONLY} != true ]; then test_vb test_published_files test_aspnetapp - test_split_build test_templates test_user test_binary diff --git a/10.0/runtime/Dockerfile.rhel9 b/10.0/runtime/Dockerfile.rhel9 index c9fd28bf7..2bd6e4dbb 100644 --- a/10.0/runtime/Dockerfile.rhel9 +++ b/10.0/runtime/Dockerfile.rhel9 @@ -5,19 +5,9 @@ ARG DOTNET_VERSION=10.0 ARG IS_CI ARG DOTNET_TARBALL -# This image provides a .NET 10.0 environment you can use to run your .NET -# applications. -EXPOSE 8080 - -ENV HOME=/opt/app-root \ - PATH=/opt/app-root/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \ - DOTNET_APP_PATH=/opt/app-root/app \ - DOTNET_DATA_PATH=/opt/app-root/data \ - DOTNET_DEFAULT_CMD=default-cmd.sh \ +ENV \ # Microsoft's images set this to enable detecting when an app is running in a container. DOTNET_RUNNING_IN_CONTAINER=true \ - # Don't download/extract docs for nuget packages - NUGET_XMLDOC_MODE=skip \ # Configure ASP.NET Core to use the exposed port ASPNETCORE_URLS=http://*:8080 \ # Like Microsoft images, set APP_UID to the UID of the non-root user. @@ -39,13 +29,11 @@ LABEL name="ubi9/dotnet-100-runtime" \ dotnet_version_major_minor="10.0" \ dotnet_version="$DOTNET_VERSION" -COPY ./root/usr/bin /usr/bin - # Install packages: # - dotnet-runtime-*: provides the .NET shared framework. -# - findutils: provides 'find' which is used by the 'fix-permissions' script. +# - tar, gzip: enable installing vsdbg into the image for debugging. RUN [ -n "${DOTNET_TARBALL}" ] || ( \ - INSTALL_PKGS="dotnet-runtime-10.0 findutils shadow-utils tar gzip" && \ + INSTALL_PKGS="dotnet-runtime-10.0 tar gzip" && \ microdnf install -y --setopt=tsflags=nodocs --setopt=install_weak_deps=0 $INSTALL_PKGS && \ # ubi-minimal doesn't include timezones, restore them. ( microdnf reinstall tzdata -y || microdnf update tzdata -y ) && \ @@ -54,7 +42,7 @@ RUN [ -n "${DOTNET_TARBALL}" ] || ( \ rm -rf /var/cache/yum/* ) # Add .NET from a tarball for CI/development. RUN [ -z "${DOTNET_TARBALL}" ] || ( \ - microdnf install -y tar gzip unzip shadow-utils libicu && \ + microdnf install -y tar gzip unzip libicu && \ ( microdnf reinstall tzdata -y || microdnf update tzdata -y ) && \ curl "${DOTNET_TARBALL}" -o /tmp/dotnet.tar.gz && \ mkdir /opt/dotnet && \ @@ -69,18 +57,10 @@ RUN [ "$IS_CI" == "true" ] || ( \ dotnet --list-runtimes | grep "Microsoft.NETCore.App ${DOTNET_VERSION} " ) # Add default user -RUN mkdir -p ${DOTNET_APP_PATH} ${DOTNET_DATA_PATH} && \ - useradd -u $APP_UID -r -g 0 -d ${HOME} -s /sbin/nologin \ - -c "Default Application User" default - -WORKDIR ${DOTNET_APP_PATH} -COPY default-cmd.sh ${DOTNET_DEFAULT_CMD} -CMD "./${DOTNET_DEFAULT_CMD}" - -# In order to drop the root user, we have to make some directories world -# writable as OpenShift default security model is to run the container under -# random UID. -RUN chown -R $APP_UID:0 /opt/app-root && fix-permissions /opt/app-root +RUN useradd -u $APP_UID -r -g 0 -d /opt/app-root -s /sbin/nologin -c "Default Application User" default && \ + install -d -m 0775 -o $APP_UID -g 0 /opt/app-root # Run container rootless. USER $APP_UID +# Set HOME for the random uid that runs the s2i app on OpenShift +ENV HOME=/opt/app-root diff --git a/10.0/runtime/README.md b/10.0/runtime/README.md index aa4c161ac..dd538730f 100644 --- a/10.0/runtime/README.md +++ b/10.0/runtime/README.md @@ -47,11 +47,6 @@ They must not to be overridden. This variable is set to `http://*:8080` to configure ASP.NET Core to use the port exposed by the image. -* **DOTNET_APP_PATH,DOTNET_DEFAULT_CMD,DOTNET_DATA_PATH** - - These variables contain the working directory (`/opt/app-root/app`), the default CMD of the runtime image (`default-cmd.sh`) - and an empty folder you can use to store your application data (`/opt/app-root/data`) and make persistent with a volume mount. - * **SSL_CERT_DIR** Used to specify a list of colon (`:`) separated directories with certificates to trust. diff --git a/10.0/runtime/test/run b/10.0/runtime/test/run index 2d2039b17..f44674291 100755 --- a/10.0/runtime/test/run +++ b/10.0/runtime/test/run @@ -47,16 +47,12 @@ test_dotnet() { test_envvars() { test_start - # DOTNET_APP_PATH - assert_equal $(docker_get_env $IMAGE_NAME DOTNET_APP_PATH) "/opt/app-root/app" - # DOTNET_DEFAULT_CMD - assert_equal $(docker_get_env $IMAGE_NAME DOTNET_DEFAULT_CMD) "default-cmd.sh" - # DOTNET_DATA_PATH - assert_equal $(docker_get_env $IMAGE_NAME DOTNET_DATA_PATH) "/opt/app-root/data" - # .NET determines culture based on LANG. It must be empty/unset to use the 'Invariant' culture assert_equal $(docker_get_env $IMAGE_NAME LANG) "" + # Expected PATH from the base image + assert_equal $(docker_get_env $IMAGE_NAME PATH) "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + # Like Microsoft images, we set this to 'true' assert_equal $(docker_get_env $IMAGE_NAME DOTNET_RUNNING_IN_CONTAINER) "true" @@ -93,13 +89,6 @@ test_default_cmd() { assert_contains_multiline "$processes" "/bin/bash" } -test_data_folder() { - test_start - - # check a writable data folder is available - assert_equal $(docker_run $IMAGE_NAME stat -c %a /opt/app-root/data) "777" -} - test_timezones() { test_start @@ -120,11 +109,17 @@ test_user() { assert_equal $(docker_run_as $IMAGE_NAME 0 whoami) "root" } +test_workingdir() { + test_start + + # Verify WORKING dir is '/', matching Microsoft images. + local working_dir=$(docker_run ${IMAGE_NAME} pwd) + assert_equal "${working_dir}" "/" +} + test_port() { test_start - # Port 8080 is exposed - assert_equal $(docker_get_exposedports $IMAGE_NAME) '{"8080/tcp":{}}' # The environment variable used by ASP.NET Core matches assert_equal $(docker_get_env $IMAGE_NAME ASPNETCORE_URLS) "http://*:8080" } @@ -196,10 +191,10 @@ test_repos() { info "Testing ${IMAGE_NAME}" if [ ${OPENSHIFT_ONLY} != true ]; then + test_workingdir test_dotnet test_envvars test_default_cmd - test_data_folder test_timezones test_debuggable test_user