diff --git a/ue4docker/build.py b/ue4docker/build.py
index 359d3c90..c9733d7a 100644
--- a/ue4docker/build.py
+++ b/ue4docker/build.py
@@ -119,16 +119,8 @@ def build():
logger.info('Detected max image size: {:.0f}GB'.format(DockerUtils.maxsize()), False)
logger.info('Visual Studio: {}'.format(config.visualStudio), False)
- # Verify that the specified base image tag is not a release that has reached End Of Life (EOL)
- 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:
+ if config.ignoreBlacklist == False and WindowsUtils.isBlacklistedWindowsHost() == True:
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)
@@ -140,9 +132,12 @@ def build():
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):
+ newer_check = WindowsUtils.isNewerBaseTag(config.hostBasetag, config.basetag)
+ if newer_check:
logger.error('Error: cannot build container images with a newer kernel version than that of the host OS!')
sys.exit(1)
+ elif newer_check is None:
+ logger.info('Warning: unable to determine whether host system is new enough to use specified base tag')
# Ensure the Docker daemon is configured correctly
requiredLimit = WindowsUtils.requiredSizeLimit()
diff --git a/ue4docker/diagnostics/base.py b/ue4docker/diagnostics/base.py
index d974118f..6575b0c3 100644
--- a/ue4docker/diagnostics/base.py
+++ b/ue4docker/diagnostics/base.py
@@ -61,8 +61,12 @@ 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())
- baseTag = basetagOverride if basetagOverride is not None else defaultBaseTag
+ hostBaseTag = WindowsUtils.getHostBaseTag()
+ baseTag = basetagOverride if basetagOverride is not None else hostBaseTag
+
+ if baseTag is None:
+ raise RuntimeError('unable to determine Windows Server Core base image tag from host system. Specify it explicitly using -basetag command-line flag')
+
buildArgs = ['--build-arg', 'BASETAG={}'.format(baseTag)]
# Use the default isolation mode unless requested otherwise
@@ -72,7 +76,7 @@ def _generateWindowsBuildArgs(self, logger, basetagOverride=None, isolationOverr
# If the user specified process isolation mode and a different base tag to the host system then warn them
prefix = self.getPrefix()
- if isolation == 'process' and baseTag != defaultBaseTag:
+ if isolation == 'process' and baseTag != hostBaseTag:
logger.info('[{}] Warning: attempting to use different Windows container/host versions'.format(prefix), False)
logger.info('[{}] when running in process isolation mode, this will usually break!'.format(prefix), False)
diff --git a/ue4docker/diagnostics/diagnostic_20gig.py b/ue4docker/diagnostics/diagnostic_20gig.py
index 7f6864c3..97572c22 100644
--- a/ue4docker/diagnostics/diagnostic_20gig.py
+++ b/ue4docker/diagnostics/diagnostic_20gig.py
@@ -14,7 +14,7 @@ def __init__(self):
# Setup our argument parser so we can use its help message output in our description text
self._parser = argparse.ArgumentParser(prog='ue4-docker diagnostics 20gig')
self._parser.add_argument('--isolation', default=None, choices=['hyperv', 'process'], help="Override the default isolation mode when testing Windows containers")
- self._parser.add_argument('-basetag', default=None, choices=WindowsUtils.getValidBaseTags(), help="Override the default base image tag when testing Windows containers")
+ self._parser.add_argument('-basetag', default=None, choices=WindowsUtils.getKnownBaseTags(), help="Override the default base image tag when testing Windows containers")
def getName(self):
'''
diff --git a/ue4docker/diagnostics/diagnostic_8gig.py b/ue4docker/diagnostics/diagnostic_8gig.py
index a7c86cc7..2fb53322 100644
--- a/ue4docker/diagnostics/diagnostic_8gig.py
+++ b/ue4docker/diagnostics/diagnostic_8gig.py
@@ -16,7 +16,7 @@ def __init__(self):
self._parser.add_argument('--linux', action='store_true', help="Use Linux containers under Windows hosts (useful when testing Docker Desktop or LCOW support)")
self._parser.add_argument('--random', action='store_true', help="Create a file filled with random bytes instead of zeroes under Windows")
self._parser.add_argument('--isolation', default=None, choices=['hyperv', 'process'], help="Override the default isolation mode when testing Windows containers")
- self._parser.add_argument('-basetag', default=None, choices=WindowsUtils.getValidBaseTags(), help="Override the default base image tag when testing Windows containers")
+ self._parser.add_argument('-basetag', default=None, choices=WindowsUtils.getKnownBaseTags(), help="Override the default base image tag when testing Windows containers")
def getName(self):
'''
diff --git a/ue4docker/diagnostics/diagnostic_network.py b/ue4docker/diagnostics/diagnostic_network.py
index 3d65a075..35338f13 100644
--- a/ue4docker/diagnostics/diagnostic_network.py
+++ b/ue4docker/diagnostics/diagnostic_network.py
@@ -13,7 +13,7 @@ def __init__(self):
self._parser = argparse.ArgumentParser(prog='ue4-docker diagnostics network')
self._parser.add_argument('--linux', action='store_true', help="Use Linux containers under Windows hosts (useful when testing Docker Desktop or LCOW support)")
self._parser.add_argument('--isolation', default=None, choices=['hyperv', 'process'], help="Override the default isolation mode when testing Windows containers")
- self._parser.add_argument('-basetag', default=None, choices=WindowsUtils.getValidBaseTags(), help="Override the default base image tag when testing Windows containers")
+ self._parser.add_argument('-basetag', default=None, choices=WindowsUtils.getKnownBaseTags(), help="Override the default base image tag when testing Windows containers")
def getName(self):
'''
diff --git a/ue4docker/infrastructure/BuildConfiguration.py b/ue4docker/infrastructure/BuildConfiguration.py
index 38db5fba..da3eda1b 100644
--- a/ue4docker/infrastructure/BuildConfiguration.py
+++ b/ue4docker/infrastructure/BuildConfiguration.py
@@ -108,7 +108,6 @@ 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)')
@@ -166,7 +165,6 @@ 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
@@ -250,30 +248,25 @@ def _generateWindowsConfig(self):
self.opts['buildgraph_args'] = self.opts.get('buildgraph_args', '') + f' -set:VS{self.visualStudio}=true'
# Determine base tag for the Windows release of the host system
- self.hostRelease = WindowsUtils.getWindowsRelease()
- self.hostBasetag = WindowsUtils.getReleaseBaseTag(self.hostRelease)
+ self.hostBasetag = WindowsUtils.getHostBaseTag()
# 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
+
+ if self.basetag is None:
+ raise RuntimeError('unable to determine Windows Server Core base image tag from host system. Specify it explicitly using -basetag command-line flag')
+
self.baseImage = 'mcr.microsoft.com/windows/servercore:' + self.basetag
self.dllSrcImage = WindowsUtils.getDllSrcImage(self.basetag)
self.prereqsTag = self.basetag + '-vs' + self.visualStudio
- # 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
+ differentKernels = self.basetag != self.hostBasetag
dockerSupportsProcess = parse_version(DockerUtils.version()['Version']) >= parse_version('18.09.0')
if not differentKernels and dockerSupportsProcess:
self.isolation = 'process'
diff --git a/ue4docker/infrastructure/WindowsUtils.py b/ue4docker/infrastructure/WindowsUtils.py
index c4bdbdf1..a68732ec 100644
--- a/ue4docker/infrastructure/WindowsUtils.py
+++ b/ue4docker/infrastructure/WindowsUtils.py
@@ -1,6 +1,7 @@
from .DockerUtils import DockerUtils
from pkg_resources import parse_version
import platform, sys
+from typing import Optional
if platform.system() == 'Windows':
import winreg
@@ -10,18 +11,22 @@ class WindowsUtils(object):
# The oldest Windows build we support
_minimumRequiredBuild = 17763
- # The latest Windows build version we recognise as a non-Insider build
- _latestReleaseBuild = 19042
+ # This lookup table is based on the list of valid tags from
+ # and list of build-to-release mapping from https://docs.microsoft.com/en-us/windows/release-health/release-information
+ _knownTagsByBuildNumber = {
+ 17763: 'ltsc2019',
+ 18362: '1903',
+ 18363: '1909',
+ 19041: '2004',
+ 19042: '20H2',
+ 19043: '21H1',
+ }
- # The list of Windows Server Core base image tags that we recognise, in ascending version number order
- _validTags = ['ltsc2019', '1903', '1909', '2004', '20H2']
+ _knownTags = list(_knownTagsByBuildNumber.values())
# The list of Windows Server and Windows 10 host OS releases that are blacklisted due to critical bugs
# (See: )
- _blacklistedReleases = ['1903', '1909']
-
- # The list of Windows Server Core container image releases that are unsupported due to having reached EOL
- _eolReleases = ['1903', '1909']
+ _blacklistedHosts = [18362, 18363]
@staticmethod
def _getVersionRegKey(subkey : str) -> str:
@@ -54,25 +59,21 @@ def systemString() -> str:
'''
Generates a verbose human-readable version string for the Windows host system
'''
- return '{} Version {} (Build {}.{})'.format(
+ return '{} (Build {}.{})'.format(
WindowsUtils._getVersionRegKey('ProductName'),
- WindowsUtils.getWindowsRelease(),
WindowsUtils.getWindowsBuild(),
WindowsUtils._getVersionRegKey('UBR')
)
@staticmethod
- def getWindowsRelease() -> str:
+ def getHostBaseTag() -> Optional[str]:
'''
- Determines the Windows 10 / Windows Server release (1607, 1709, 1803, etc.) of the Windows host system
+ Retrieves the tag for the Windows Server Core base image matching the host Windows system
'''
- try:
- # Starting with Windows 20H2 (also known as 2009), Microsoft stopped updating ReleaseId
- # and instead updates DisplayVersion
- return WindowsUtils._getVersionRegKey('DisplayVersion')
- except FileNotFoundError:
- # Fallback to ReleaseId for pre-20H2 releases that didn't have DisplayVersion
- return WindowsUtils._getVersionRegKey('ReleaseId')
+
+ hostBuild = WindowsUtils.getWindowsBuild()
+
+ return WindowsUtils._knownTagsByBuildNumber.get(hostBuild)
@staticmethod
def getWindowsBuild() -> int:
@@ -82,23 +83,14 @@ def getWindowsBuild() -> int:
return sys.getwindowsversion().build
@staticmethod
- def isBlacklistedWindowsVersion(release=None):
+ def isBlacklistedWindowsHost() -> bool:
'''
- Determines if the specified Windows release is one with bugs that make it unsuitable for use
+ Determines if host Windows version is one with bugs that make it unsuitable for use
(defaults to checking the host OS release if one is not specified)
'''
dockerVersion = parse_version(DockerUtils.version()['Version'])
- release = WindowsUtils.getWindowsRelease() if release is None else release
- return release in WindowsUtils._blacklistedReleases and dockerVersion < parse_version('19.03.6')
-
- @staticmethod
- def isEndOfLifeWindowsVersion(release=None):
- '''
- Determines if the specified Windows release is one that has reached End Of Life (EOL)
- (defaults to checking the host OS release if one is not specified)
- '''
- release = WindowsUtils.getWindowsRelease() if release is None else release
- return release in WindowsUtils._eolReleases
+ build = WindowsUtils.getWindowsBuild()
+ return build in WindowsUtils._blacklistedHosts and dockerVersion < parse_version('19.03.6')
@staticmethod
def isWindowsServer() -> bool:
@@ -108,28 +100,6 @@ def isWindowsServer() -> bool:
# TODO: Replace this with something more reliable
return 'Windows Server' in WindowsUtils._getVersionRegKey('ProductName')
- @staticmethod
- def isInsiderPreview() -> bool:
- '''
- Determines if the Windows host system is a Windows Insider preview build
- '''
- return WindowsUtils.getWindowsBuild() > WindowsUtils._latestReleaseBuild
-
- @staticmethod
- def getReleaseBaseTag(release: str) -> str:
- '''
- Retrieves the tag for the Windows Server Core base image matching the specified Windows 10 / Windows Server release
- '''
-
- # For Windows Insider preview builds, build the latest release tag
- if WindowsUtils.isInsiderPreview():
- return WindowsUtils._validTags[-1]
-
- # This lookup table is based on the list of valid tags from
- return {
- '1809': 'ltsc2019',
- }.get(release, release)
-
@staticmethod
def getDllSrcImage(basetag: str) -> str:
'''
@@ -142,22 +112,18 @@ def getDllSrcImage(basetag: str) -> str:
return f'mcr.microsoft.com/windows:{tag}'
@staticmethod
- def getValidBaseTags() -> [str]:
- '''
- Returns the list of valid tags for the Windows Server Core base image, in ascending chronological release order
- '''
- return WindowsUtils._validTags
-
- @staticmethod
- def isValidBaseTag(tag: str) -> bool:
+ def getKnownBaseTags() -> [str]:
'''
- Determines if the specified tag is a valid Windows Server Core base image tag
+ Returns the list of known tags for the Windows Server Core base image, in ascending chronological release order
'''
- return tag in WindowsUtils._validTags
+ return WindowsUtils._knownTags
@staticmethod
- def isNewerBaseTag(older: str, newer: str) -> bool:
+ def isNewerBaseTag(older: str, newer: str) -> Optional[bool]:
'''
Determines if the base tag `newer` is chronologically newer than the base tag `older`
'''
- return WindowsUtils._validTags.index(newer) > WindowsUtils._validTags.index(older)
+ try:
+ return WindowsUtils._knownTags.index(newer) > WindowsUtils._knownTags.index(older)
+ except ValueError:
+ return None