Skip to content

Commit

Permalink
resolves #164 un-hardcode list of Windows versions
Browse files Browse the repository at this point in the history
Partially resolves #138 (support for insider base image is still missing)

This commit:
1. Drops the list of valid Windows versions. Instead, ue4-docker now trusts user input and will fail during image download if user enters something nonexistent
2. Drops 1603->ltsc2016, 1809->ltsc2019 and 2009->20H2 renaming. Instead, ue4-docker directly maps host OS release to Windows Server Core image tag
3. However, ue4-docker now uses advanced logic to determine host OS release. It tries to use DisplayName registry key and fallbacks to ReleaseId if DisplayName doesn't exist. This allows to properly detect 20H2 and 21H1 releases.

On the negative side, some checks are lost:
1. It is no longer possible to check that container version is newer that host OS. Though is should still be rejected by Hyper-V
2. ue4-docker no longer prevents user from using suffix that collides with Windows Server Core image tags
  • Loading branch information
slonopotamus committed May 21, 2021
1 parent 4d183cd commit a8f4813
Show file tree
Hide file tree
Showing 9 changed files with 71 additions and 161 deletions.
26 changes: 11 additions & 15 deletions ue4docker/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,22 +112,23 @@ def build():
# Provide the user with feedback so they are aware of the Windows-specific values being used
logger.info('WINDOWS CONTAINER SETTINGS', False)
logger.info('Isolation mode: {}'.format(config.isolation), False)
logger.info('Base OS image tag: {} (host OS is {})'.format(config.basetag, WindowsUtils.systemStringShort()), False)
logger.info('Base OS image tag: {}'.format(config.basetag), False)
logger.info('Host OS: {}'.format(WindowsUtils.systemString()), False)
logger.info('Memory limit: {}'.format('No limit' if config.memLimit is None else '{:.2f}GB'.format(config.memLimit)), False)
logger.info('Detected max image size: {:.0f}GB'.format(DockerUtils.maxsize()), False)
logger.info('Directory to copy DLLs from: {}\n'.format(config.dlldir), False)

# Verify that the specified base image tag is not a release that has reached End Of Life (EOL)
if WindowsUtils.isEndOfLifeWindowsVersion(config.basetag) == True:
if not config.ignoreEOL and WindowsUtils.isEndOfLifeWindowsVersion(config.basetag):
logger.error('Error: detected EOL base OS image tag: {}'.format(config.basetag), False)
logger.error('This version of Windows has reached End Of Life (EOL), which means', False)
logger.error('Microsoft no longer supports or maintains container base images for it.', False)
logger.error('You will need to use a base image tag for a supported version of Windows.', False)
sys.exit(1)

# Verify that the host OS is not a release that is blacklisted due to critical bugs
if config.ignoreBlacklist == False and WindowsUtils.isBlacklistedWindowsVersion() == True:
logger.error('Error: detected blacklisted host OS version: {}'.format(WindowsUtils.systemStringShort()), False)
if not config.ignoreBlacklist and WindowsUtils.isBlacklistedWindowsVersion():
logger.error('Error: detected blacklisted host OS version: {}'.format(WindowsUtils.systemString()), False)
logger.error('', False)
logger.error('This version of Windows contains one or more critical bugs that', False)
logger.error('render it incapable of successfully building UE4 container images.', False)
Expand All @@ -136,15 +137,10 @@ def build():
logger.error('For more information, see:', False)
logger.error('https://unrealcontainers.com/docs/concepts/windows-containers', False)
sys.exit(1)

# Verify that the user is not attempting to build images with a newer kernel version than the host OS
if WindowsUtils.isNewerBaseTag(config.hostBasetag, config.basetag):
logger.error('Error: cannot build container images with a newer kernel version than that of the host OS!')
sys.exit(1)


# Check if the user is building a different kernel version to the host OS but is still copying DLLs from System32
differentKernels = WindowsUtils.isInsiderPreview() or config.basetag != config.hostBasetag
if config.pullPrerequisites == False and differentKernels == True and config.dlldir == config.defaultDllDir:
differentKernels = config.basetag != config.hostBasetag
if not config.pullPrerequisites and differentKernels and config.dlldir == config.defaultDllDir:
logger.error('Error: building images with a different kernel version than the host,', False)
logger.error('but a custom DLL directory has not specified via the `-dlldir=DIR` arg.', False)
logger.error('The DLL files will be the incorrect version and the container OS will', False)
Expand Down Expand Up @@ -239,7 +235,7 @@ def build():
# (This is the only image that does not use any user-supplied tag suffix, since the tag always reflects any customisations)
prereqsArgs = ['--build-arg', 'BASEIMAGE=' + config.baseImage]
if config.containerPlatform == 'windows':
prereqsArgs = prereqsArgs + ['--build-arg', 'HOST_VERSION=' + WindowsUtils.getWindowsBuild()]
prereqsArgs = prereqsArgs + ['--build-arg', 'HOST_BUILD=' + str(WindowsUtils.getWindowsBuild())]

# Build or pull the UE4 build prerequisites image (don't pull it if we're copying Dockerfiles to an output directory)
if config.layoutDir is None and config.pullPrerequisites == True:
Expand Down
3 changes: 1 addition & 2 deletions ue4docker/diagnostics/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,7 @@ def _generateWindowsBuildArgs(self, logger, basetagOverride=None, isolationOverr
'''

# Determine the appropriate container image base tag for the host system release unless the user specified a base tag
buildArgs = []
defaultBaseTag = WindowsUtils.getReleaseBaseTag(WindowsUtils.getWindowsRelease())
defaultBaseTag = WindowsUtils.getWindowsRelease()
baseTag = basetagOverride if basetagOverride is not None else defaultBaseTag
buildArgs = ['--build-arg', 'BASETAG={}'.format(baseTag)]

Expand Down
2 changes: 1 addition & 1 deletion ue4docker/diagnostics/diagnostic_maxsize.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def run(self, logger, args=[]):
return False

# Verify that we are running Windows Server or Windows 10 version 1903 or newer
if WindowsUtils.getWindowsVersion()['patch'] < 18362:
if WindowsUtils.getWindowsBuild() < 18362:
logger.info('[maxsize] This diagnostic only applies to Windows Server and Windows 10 version 1903 and newer.', False)
return True

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ RUN choco install -y 7zip curl && choco install -y python --version=3.7.5
COPY *.dll C:\GatheredDlls\

# Verify that the DLL files copied from the host can be loaded by the container OS
ARG HOST_VERSION
ARG HOST_BUILD
RUN pip install pywin32
COPY copy.py verify-host-dlls.py C:\
RUN C:\copy.py "C:\GatheredDlls\*.dll" C:\Windows\System32\
RUN python C:\verify-host-dlls.py %HOST_VERSION% C:\GatheredDlls
RUN python C:\verify-host-dlls.py %HOST_BUILD% C:\GatheredDlls

# Gather the required DirectX runtime files, since Windows Server Core does not include them
RUN curl --progress-bar -L "https://download.microsoft.com/download/8/4/A/84A35BF1-DAFE-4AE8-82AF-AD2AE20B6B14/directx_Jun2010_redist.exe" --output %TEMP%\directx_redist.exe
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import glob, os, platform, sys, win32api, winreg
import glob, os, sys, win32api


# Adapted from the code in this SO answer: <https://stackoverflow.com/a/7993095>
def getDllVersion(dllPath):
Expand All @@ -10,20 +11,10 @@ def getDllVersion(dllPath):
info['FileVersionLS'] % 65536
)

def getVersionRegKey(subkey):
key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion')
value = winreg.QueryValueEx(key, subkey)
winreg.CloseKey(key)
return value[0]

def getOsVersion():
version = platform.win32_ver()[1]
build = getVersionRegKey('BuildLabEx').split('.')[1]
return '{}.{}'.format(version, build)

# Print the host and container OS build numbers
print('Host OS build number: {}'.format(sys.argv[1]))
print('Container OS build number: {}'.format(getOsVersion()))
print('Container OS build number: {}'.format(sys.getwindowsversion().build))
sys.stdout.flush()

# Verify each DLL file in the directory specified by our command-line argument
Expand Down
2 changes: 1 addition & 1 deletion ue4docker/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

def _osName(dockerInfo):
if platform.system() == 'Windows':
return WindowsUtils.systemStringLong()
return WindowsUtils.systemString()
elif platform.system() == 'Darwin':
return DarwinUtils.systemString()
else:
Expand Down
19 changes: 6 additions & 13 deletions ue4docker/infrastructure/BuildConfiguration.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ def addArguments(parser):
parser.add_argument('--combine', action='store_true', help='Combine generated Dockerfiles into a single multi-stage build Dockerfile')
parser.add_argument('--monitor', action='store_true', help='Monitor resource usage during builds (useful for debugging)')
parser.add_argument('-interval', type=float, default=20.0, help='Sampling interval in seconds when resource monitoring has been enabled using --monitor (default is 20 seconds)')
parser.add_argument('--ignore-eol', action='store_true', help='Run builds even on EOL versions of Windows (advanced use only)')
parser.add_argument('--ignore-blacklist', action='store_true', help='Run builds even on blacklisted versions of Windows (advanced use only)')
parser.add_argument('-v', '--verbose', action='store_true', help='Enable verbose output during builds (useful for debugging)')

Expand Down Expand Up @@ -150,6 +151,7 @@ def __init__(self, parser, argv):
self.excludedComponents = set(self.args.exclude)
self.baseImage = None
self.prereqsTag = None
self.ignoreEOL = self.args.ignore_eol
self.ignoreBlacklist = self.args.ignore_blacklist
self.verbose = self.args.verbose
self.layoutDir = self.args.layout
Expand Down Expand Up @@ -226,30 +228,21 @@ def _generateWindowsConfig(self):
self.dlldir = self.args.dlldir if self.args.dlldir is not None else self.defaultDllDir

# Determine base tag for the Windows release of the host system
self.hostRelease = WindowsUtils.getWindowsRelease()
self.hostBasetag = WindowsUtils.getReleaseBaseTag(self.hostRelease)
self.hostBasetag = WindowsUtils.getWindowsRelease()

# Store the tag for the base Windows Server Core image
self.basetag = self.args.basetag if self.args.basetag is not None else self.hostBasetag
self.baseImage = 'mcr.microsoft.com/windows/servercore:' + self.basetag
self.prereqsTag = self.basetag

# Verify that any user-specified base tag is valid
if WindowsUtils.isValidBaseTag(self.basetag) == False:
raise RuntimeError('unrecognised Windows Server Core base image tag "{}", supported tags are {}'.format(self.basetag, WindowsUtils.getValidBaseTags()))

# Verify that any user-specified tag suffix does not collide with our base tags
if WindowsUtils.isValidBaseTag(self.suffix) == True:
raise RuntimeError('tag suffix cannot be any of the Windows Server Core base image tags: {}'.format(WindowsUtils.getValidBaseTags()))


# If the user has explicitly specified an isolation mode then use it, otherwise auto-detect
if self.args.isolation is not None:
self.isolation = self.args.isolation
else:

# If we are able to use process isolation mode then use it, otherwise fallback to the Docker daemon's default isolation mode
differentKernels = WindowsUtils.isInsiderPreview() or self.basetag != self.hostBasetag
hostSupportsProcess = WindowsUtils.isWindowsServer() or int(self.hostRelease) >= 1809
differentKernels = self.basetag != self.hostBasetag
hostSupportsProcess = WindowsUtils.supportsProcessIsolation()
dockerSupportsProcess = parse_version(DockerUtils.version()['Version']) >= parse_version('18.09.0')
if not differentKernels and hostSupportsProcess and dockerSupportsProcess:
self.isolation = 'process'
Expand Down
Loading

0 comments on commit a8f4813

Please sign in to comment.