From 8a9e61251ed731514781890c6096a3d9b1266831 Mon Sep 17 00:00:00 2001 From: Andreas Motl Date: Wed, 8 Jun 2022 00:21:26 +0200 Subject: [PATCH 01/14] [windows] Make Windows runner backend production ready Add support for Windows Server Core 2019 and friends. - windows/servercore:ltsc2019 - windows/nanoserver:1809 --- CHANGES.rst | 4 + README.rst | 50 ++++++++- doc/cratedb.rst | 46 +++++++- doc/winrunner.rst | 57 +++++++--- postroj/cli.py | 3 +- postroj/runner.py | 38 ------- postroj/winrunner.Dockerfile | 20 ++++ postroj/winrunner.py | 173 ++++++++++++++++++++--------- pyproject.toml | 1 - racker/cli.py | 40 ++++++- setup.py | 1 + testing/racker/test_run_windows.py | 82 ++++++++++++++ 12 files changed, 403 insertions(+), 112 deletions(-) delete mode 100644 postroj/runner.py create mode 100644 postroj/winrunner.Dockerfile create mode 100644 testing/racker/test_run_windows.py diff --git a/CHANGES.rst b/CHANGES.rst index 5b28851..ecc3de5 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -11,6 +11,10 @@ in progress - Improve platform guards and naming things - Improve central command invocation function - Improve documentation +- Make Windows runner subsystem production ready. +- Add support for Windows Server Core 2019 and friends, like + ``windows/servercore:ltsc2019``, ``windows/nanoserver:1809``, or + ``eclipse-temurin:17-jdk``. 2022-05-20 0.2.0 diff --git a/README.rst b/README.rst index 758bff3..45d0f0d 100644 --- a/README.rst +++ b/README.rst @@ -109,6 +109,13 @@ another one for Windows. `systemd-nspawn`_. Provisioning of additional software is performed using the native package manager of the corresponding Linux distribution. +- For running Windows operating systems containers, Racker uses `Vagrant`_, + `Docker`_, and `Windows Docker Machine`_. The virtual machine base image is + acquired from `Vagrant Cloud`_, container images are acquired from the + `Microsoft Container Registry`_. For provisioning additional software, the + `Chocolatey`_ package manager is used. All of cmd, PowerShell and Bash are + pre-installed on the container images. + Operating system coverage ------------------------- @@ -132,6 +139,11 @@ Linux - SUSE SLES 15 and BCI:latest - Ubuntu LTS 20 and 22 (focal, jammy) +Windows +....... +- Windows Server Core LTSC 2016, 2019, and 2022 +- Windows Nano Server 1809 and LTSC 2022 + Prior art --------- @@ -212,7 +224,7 @@ Racker The ``racker`` program aims to resemble the semantics of Docker by providing a command line interface compatible with the ``docker`` command. -:: +Linux examples:: # Invoke the vanilla Docker `hello-world` image. # FIXME: Does not work yet. @@ -244,6 +256,42 @@ command line interface compatible with the ``docker`` command. time echo "hello world" | racker run -it --rm fedora:37 cat /dev/stdin > hello cat hello +Windows examples:: + + # Windows OS images, mostly LTSC (Long-Term Servicing Channel). + # Please note the download sizes. + # Nanoserver: 250 MB, Servercore: 6 GB, Servercore with Java: 7 GB, Windows: 15 GB + + # Launch an interactive command prompt (cmd, PowerShell or Bash). + racker --verbose run -it --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2019-amd64 powershell + + # Launch a single command. + racker --verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2019-amd64 -- 'powershell -Command {echo "Hello, world."}' + racker --verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2019-amd64 'sh -c "echo Hello, world."' + + # Inquire system information. + racker run --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2019-amd64 wmic os get caption + racker run --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2019-amd64 'powershell -Command Get-ComputerInfo' + + # Use stdin and stdout, with time keeping. + time racker --verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows/nanoserver:1809-amd64 cmd /C echo "Hello, world." > hello + cat hello + + # Invoke a Java command prompt (JShell) with different Java versions. + racker run -it --rm --platform=windows/amd64 openjdk:18-windowsservercore-1809 jshell + racker run -it --rm --platform=windows/amd64 eclipse-temurin:18-jdk jshell + System.out.println("OS: " + System.getProperty("os.name") + ", version " + System.getProperty("os.version")) + System.out.println("Java: " + System.getProperty("java.vendor") + ", version " + System.getProperty("java.version")) + /exit + + # Windows Nano Server. + racker --verbose run -it --rm --platform=windows/amd64 mcr.microsoft.com/windows/nanoserver:1809-amd64 cmd + racker --verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows/nanoserver:1809-amd64 cmd /C echo Hello, world. + + # Full Windows. + racker --verbose run -it --rm --platform=windows/amd64 mcr.microsoft.com/windows:1809-amd64 cmd + + Postroj ======= diff --git a/doc/cratedb.rst b/doc/cratedb.rst index 45fc533..8c88ce3 100644 --- a/doc/cratedb.rst +++ b/doc/cratedb.rst @@ -1,12 +1,52 @@ -######################### -Using postroj for CrateDB -######################### +####################################### +Using Racker and Postroj for CrateDB CI +####################################### .. note:: This is still a work in progress. +************* +Prerequisites +************* + +When creating the "Windows Docker Machine" virtual machine, the program will +configure it to use 6 VPUs and 6144 MB system memory. + +In order to adjust those values, use these environment variables before +invoking the later commands:: + + export RACKER_VM_VCPUS=12 + export RACKER_VM_MEMORY=8192 + +If you want to adjust the values after the initial deployment, you will have to +reset the "Windows Docker Machine" installation directory. For example, it is: + +- On Linux: ``/root/.local/state/racker/windows-docker-machine`` +- On macOS: ``/Users/amo/Library/Application Support/racker/windows-docker-machine`` + + + +********** +racker run +********** + +Purpose: Invoke programs in a Java/OpenJDK environment, within a +virtualized/dockerized Windows installation. + +Run the CrateDB test suite on OpenJDK 18 (Eclipse Temurin):: + + time racker run --rm --platform=windows/amd64 eclipse-temurin:18-jdk \ + "sh -c 'mkdir /c/src; cd /c/src; git clone https://github.com/crate/crate --depth=1; cd crate; ./gradlew --no-daemon --parallel -PtestForks=2 :server:test -Dtests.crate.run-windows-incompatible=false --stacktrace'" + +Invoke a Java command prompt (JShell) with OpenJDK 18:: + + racker run -it --rm --platform=windows/amd64 eclipse-temurin:18-jdk jshell + System.out.println("OS: " + System.getProperty("os.name") + ", version " + System.getProperty("os.version")) + System.out.println("Java: " + System.getProperty("java.vendor") + ", version " + System.getProperty("java.version")) + + **************** postroj pkgprobe **************** diff --git a/doc/winrunner.rst b/doc/winrunner.rst index de388d9..97110fc 100644 --- a/doc/winrunner.rst +++ b/doc/winrunner.rst @@ -1,6 +1,6 @@ -################# -postroj winrunner -################# +############## +Windows runner +############## .. note:: @@ -11,16 +11,13 @@ postroj winrunner About ***** -- https://github.com/PyTables/PyTables/pull/872#issuecomment-773535041 - +Launch an interactive command prompt (cmd, PowerShell or Bash) within a Windows +environment or invoke programs non-interactively. -******** -Synopsis -******** -:: +References +========== - # Basic usage. - postroj invoke --system=windows-1809 -- cmd /C echo hello +- https://github.com/PyTables/PyTables/pull/872#issuecomment-773535041 ***** @@ -28,8 +25,8 @@ Setup ***** :: - # Install VirtualBox, Vagrant and Docker. - brew install virtualbox docker vagrant + # Install VirtualBox, Vagrant, and Docker. + brew install virtualbox vagrant docker # Install Windows VM with Docker environment. git clone https://github.com/StefanScherer/windows-docker-machine @@ -47,6 +44,38 @@ Setup Usage ***** +Basic usage:: + + racker run --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2019-amd64 \ + cmd /C echo "Hello, world." + +Install software packages using `Chocolatey`_:: + + racker run -it --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2019-amd64 \ + powershell + Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')) + choco install --yes git + Install-ChocolateyPath -PathToInstall "$($env:SystemDrive)\Program Files\Git\bin" + + +************* +Miscellaneous +************* + +Manipulating ``PATH`` +===================== + +Display the content of the ``PATH`` environment variable:: + + echo %PATH% + (Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' -Name Path).Path + +Set the content of the ``PATH`` environment variable:: + + setx PATH "$env:path;$($env:SystemDrive)\Program Files\Git\bin" -m + [Environment]::SetEnvironmentVariable("Path", $env:Path + ";$($env:SystemDrive)\Program Files\Git\bin", "Machine") + + Which shell spawns faster? ========================== :: @@ -79,3 +108,5 @@ Terminate an environment:: docker --context=2019-box ps docker --context=2019-box stop 5e2fe406ccbc + +.. _Chocolatey: https://chocolatey.org/ diff --git a/postroj/cli.py b/postroj/cli.py index d6583c4..2558dfe 100644 --- a/postroj/cli.py +++ b/postroj/cli.py @@ -4,7 +4,7 @@ import click -from postroj import pkgprobe, runner, selftest, winrunner +from postroj import pkgprobe, selftest from postroj.api import pull_multiple_images, pull_single_image from postroj.registry import list_images from postroj.util import boot @@ -48,6 +48,5 @@ def cli_pull(ctx: click.Context, name: str, pull_all: bool = False): cli.add_command(cmd=cli_list_images, name="list-images") cli.add_command(cmd=cli_pull, name="pull") -cli.add_command(cmd=runner.invoke, name="invoke") cli.add_command(cmd=pkgprobe.main, name="pkgprobe") cli.add_command(cmd=selftest.selftest_main, name="selftest") diff --git a/postroj/runner.py b/postroj/runner.py deleted file mode 100644 index aacb60c..0000000 --- a/postroj/runner.py +++ /dev/null @@ -1,38 +0,0 @@ -# -*- coding: utf-8 -*- -# (c) 2022 Andreas Motl -import logging -import sys - -import click - -from postroj.winrunner import WinRunner -from racker.cli import racker_run - -logger = logging.getLogger(__name__) - - -@click.command() -@click.option("--system", type=str) -@click.option("--cpus", type=int) -@click.option("--memory", type=str) -@click.option("--mount", type=str) -@click.argument("command", nargs=-1, type=str) -@click.pass_context -def invoke(ctx, system, cpus, memory, mount, command): - """ - Run a command within a designated system environment - """ - # TODO: Propagate and implement `cpus`, `memory` and `mount`. See - command = " ".join(command) - if system == "windows-1809": - runner = WinRunner() - runner.setup() - runner.start() - outcome = runner.run(command) - sys.stdout.write(outcome) - else: - raise NotImplementedError(f'Runtime system "{system}" not supported yet') - - -if __name__ == "__main__": - racker_run() diff --git a/postroj/winrunner.Dockerfile b/postroj/winrunner.Dockerfile new file mode 100644 index 0000000..a813e23 --- /dev/null +++ b/postroj/winrunner.Dockerfile @@ -0,0 +1,20 @@ +# Provision a Windows operating system image with additional software. +# Automatically installs the open source version of the Chocolatey package manager. +# By default, it installs `git`, `curl`, and `wget`. + +ARG BASE_IMAGE + +FROM ${BASE_IMAGE} + +# Install the Chocolatey package manager. +RUN powershell -Command Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')) + +# Install essential and convenience programs. +# TODO: Verify that the right `curl` program has priority within the program search path. +RUN choco install --yes git curl wget + +# Make `bash` (from Git Bash, MINGW64) available on the program search path. +RUN powershell ([Environment]::SetEnvironmentVariable('Path', $env:Path + ';' + $($env:SystemDrive) + '\Program Files\Git\bin', 'Machine')) + +# Display the program search path. +RUN powershell echo \"(Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' -Name Path).Path\" diff --git a/postroj/winrunner.py b/postroj/winrunner.py index b4eb124..1bfe762 100644 --- a/postroj/winrunner.py +++ b/postroj/winrunner.py @@ -1,56 +1,112 @@ # -*- coding: utf-8 -*- # (c) 2022 Andreas Motl """ -A convenience wrapper around Windows Docker Machine. +A convenience wrapper around Windows Docker Machine by Stefan Scherer. https://github.com/StefanScherer/windows-docker-machine """ import json -import shlex +import logging +import os import subprocess +import tempfile from pathlib import Path from urllib.parse import urlparse -import click +import appdirs +import pkg_resources -from postroj.util import port_is_up +from postroj.util import port_is_up, cmd, fix_tty + +logger = logging.getLogger(__name__) class WinRunner: BOX = "2019-box" + VCPUS = os.environ.get("RACKER_VM_VCPUS", 6) + MEMORY = os.environ.get("RACKER_VM_MEMORY", 6144) + + def __init__(self, image: str): + self.image_base = image + self.image_real = "racker-runtime/" + self.image_base.replace("/", "-") - def __init__(self): - self.workdir = Path.home() / "postroj" - self.workdir.mkdir(exist_ok=True) + self.workdir = Path(appdirs.user_state_dir(appname="racker")) + self.workdir.mkdir(exist_ok=True, parents=True) self.wdmdir = self.workdir / "windows-docker-machine" def setup(self): + """ + Prepare virtual machine and adjust system resources. + """ if not self.wdmdir.exists(): - click.echo("Installing Windows Docker Machine") + logger.info(f"Installing Windows Docker Machine into {self.wdmdir}") command = f""" - cd {self.workdir} + cd '{self.workdir}' git clone https://github.com/StefanScherer/windows-docker-machine cd windows-docker-machine #ls -alF - sed -i 's/v.memory = 2048/v.memory = 8192/' Vagrantfile - sed -i 's/v.cpus = 2/v.cpus = 8/' Vagrantfile + sed -i 's/v.cpus = [0-9]\+/v.cpus = {self.VCPUS}/' Vagrantfile + sed -i 's/v.memory = [0-9]\+/v.memory = {self.MEMORY}/' Vagrantfile + sed -i 's/v.maxmemory = [0-9]\+/v.maxmemory = {self.MEMORY}/' Vagrantfile """ - run(command, shell=True) + hshell(command) else: - click.echo("Windows Docker Machine already installed") + logger.info(f"Windows Docker Machine already installed into {self.wdmdir}") def start(self): + """ + Start the "Windows Docker Machine" virtual machine. + + - Launch a virtual machine using Vagrant. + - Connect to Docker daemon on virtual machine. + - Provision the operating system image with additional software. + """ if self.docker_context_online(): - click.echo("Docker context is online") + logger.info("Docker context is online") else: - click.echo("Docker context is offline, starting VirtualBox VM with Vagrant") - run(f"vagrant up --provider=virtualbox {self.BOX}", cwd=self.wdmdir) + logger.info("Docker context is offline, starting VirtualBox VM with Vagrant") + cmd(f"vagrant provision {self.BOX}", cwd=self.wdmdir, use_stderr=True) + cmd(f"vagrant up --provider=virtualbox {self.BOX}", cwd=self.wdmdir, use_stderr=True) + + logger.info("Pinging Docker context") + if not self.docker_context_online(): + raise IOError(f"Unable to bring up Docker context {self.BOX}") + + # Attention: This can run into 60 second timeouts. + # TODO: Use ``with stopit.ThreadingTimeout(timeout) as to_ctx_mgr``. + # TODO: Make timeout values configurable. + # https://github.com/moby/moby/blob/0e04b514fb/integration-cli/docker_cli_run_test.go + cmd(f"docker --context={self.BOX} ps", capture=True) + + if "nanoserver" in self.image_base: + self.image_real = self.image_base + else: + self.provision_image() + + def provision_image(self): + """ + Provide an operating system image by building a Docker image using `winrunner.Dockerfile`. - click.echo("Pinging Docker context") - run("docker --context=2019-box ps") + - Provision a Windows operating system image with additional software. + - Automatically installs the open source version of the Chocolatey package manager. + - By default, it installs `git`, `curl`, and `wget`. + """ + + logger.info(f"Provisioning Docker image for Windows environment based on {self.image_base}") + dockerfile = pkg_resources.resource_filename("postroj", "winrunner.Dockerfile") + tmpdir = tempfile.mkdtemp() + command = f"docker --context={self.BOX} build --platform=windows/amd64 " \ + f"--file={dockerfile} --build-arg=BASE_IMAGE={self.image_base} --tag={self.image_real} {tmpdir}" + logger.debug(f"Running command: {command}") + try: + hcmd(command) + except subprocess.CalledProcessError: + raise + finally: + os.rmdir(tmpdir) def cmd(self, command): command = f"cmd /C {command}" @@ -60,45 +116,56 @@ def powershell(self, command): command = f"powershell -Command {command}" return self.run(command) - def run(self, command, strip_armor=True, translate_newlines=True): - click.echo(f"Running command: {command}") - command = f"docker --context={self.BOX} run -it --rm openjdk:17-windowsservercore-1809 {command}" - outcome = run(command) - if strip_armor: - """ - b'\x1b[2J\x1b[?25l\x1b[m\x1b[H\r\n\r\n...\r\n\x1b[H\x1b]0;C:\\Windows\\system32\\cmd.exe\x00\x07\x1b[?25h\x1b[?25lhello \r\n\x1b[?25h' - """ - prefix = "\x1b[?25h\x1b[?25l" - suffix = "\x1b[?25h" - cutoff_left = outcome.find(prefix) + len(prefix) - cutoff_right = outcome.rfind(suffix) - outcome = outcome[cutoff_left:cutoff_right] - if translate_newlines: - outcome = outcome.replace("\r\n", "\n") - # print(outcome.encode()) - return outcome + def run(self, command, interactive: bool = False, tty: bool = False): + logger.info(f"Running guest command: {command}") + + option_interactive = "" + if interactive or tty: + option_interactive = "-it" + + command = f"docker --context={self.BOX} run {option_interactive} --rm {self.image_real} {command}" + + # When an interactive prompt is requested, spawn a shell without further ado. + if interactive or tty: + ccmd(command, use_pty=True) + + # Otherwise, capture stdout and mangle its output. + else: + outcome = cmd(command) + return outcome def docker_context_online(self): """ - Test if a Docker context is online. + Test if the Docker context is online. """ - response = json.loads(run(f"docker context inspect {self.BOX}")) - address = urlparse(response[0]["Endpoints"]["docker"]["Host"]) + logger.info(f"Checking connectivity to Docker daemon in Windows context '{self.BOX}'") + try: + response = cmd(f"docker context inspect {self.BOX}", capture=True) + except: + logger.warning(f"Docker context {self.BOX} not online or not created yet") + return False + + data = json.loads(response.stdout) + address = urlparse(data[0]["Endpoints"]["docker"]["Host"]) + + logger.info(f"Checking TCP connectivity to {address.hostname}:{address.port}") return port_is_up(address.hostname, address.port) -def run(command, shell=False, cwd=None): - """ - Generic routine to run command within container. - - STDERR will be displayed, STDOUT will be captured. - """ - # print(f"Running command: {command}") - # command = f""" - # systemd-run --machine={machine} --wait --quiet --pipe {command} - # """ - if shell: - output = subprocess.check_output(command, shell=shell, cwd=cwd) - else: - output = subprocess.check_output(shlex.split(command), cwd=cwd) - return output.decode() +def hcmd(command, cwd=None, silent=False): + logger.debug(f"Running command: {command}") + return cmd(command, cwd=cwd, use_stderr=True) + + +def hshell(command, cwd=None): + logger.debug(f"Running command: {command}") + #return cmd(command, cwd=cwd, passthrough=True).stdout + return subprocess.check_output(command, shell=True, cwd=cwd).decode() + + +def ccmd(command, use_pty=False): + logger.debug(f"Running command: {command}") + p = cmd(command=command, use_pty=use_pty) + stdout = p.stdout + fix_tty() + return stdout diff --git a/pyproject.toml b/pyproject.toml index b9fb4e7..1404b88 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,6 @@ markers = [ [tool.coverage.run] omit = [ "testing/*", - "postroj/winrunner.py", ] [tool.coverage.report] diff --git a/racker/cli.py b/racker/cli.py index 5ae0c0d..cf55535 100644 --- a/racker/cli.py +++ b/racker/cli.py @@ -12,6 +12,7 @@ from postroj.container import PostrojContainer from postroj.exceptions import ProvisioningError from postroj.util import boot, subprocess_get_error_message +from postroj.winrunner import WinRunner from racker.image import ImageLibrary logger = logging.getLogger(__name__) @@ -47,10 +48,11 @@ def racker_pull(ctx, name: str): @click.option("--interactive", "-i", is_flag=True) @click.option("--tty", "-t", is_flag=True) @click.option("--rm", is_flag=True) +@click.option("--platform", type=str, required=False) @click.argument("image", type=str, required=False) @click.argument("command", nargs=-1, type=str) @click.pass_context -def racker_run(ctx, interactive: bool, tty: bool, rm: bool, image: str, command: str): +def racker_run(ctx, interactive: bool, tty: bool, rm: bool, platform: str, image: str, command: str): """ Spawn a container and run a command on it. Aims to be compatible with `docker run`. @@ -84,6 +86,42 @@ def racker_run(ctx, interactive: bool, tty: bool, rm: bool, image: str, command: if interactive or tty: use_pty = True + # Use a different subsystem for running Windows containers on Linux or + # macOS. + if platform is None: + pass + + elif platform == "windows/amd64": + logger.info(f"Preparing runtime environment for platform {platform} and image {image}") + runner = WinRunner(image=image) + try: + with redirect_stdout(sys.stderr): + with redirect_stderr(sys.stderr): + runner.setup() + runner.start() + except subprocess.CalledProcessError as ex: + message = subprocess_get_error_message(exception=ex) + logger.critical(f"Launching container failed. {message}") + # subprocess_forward_stderr_stdout(exception=ex) + raise SystemExit(ex.returncode) + + logger.info(f"Invoking command '{command}' on {image}") + try: + runner.run(command, interactive=interactive, tty=tty) + except subprocess.CalledProcessError as ex: + message = subprocess_get_error_message(exception=ex) + logger.critical(f"Running command in Windows container on image {image} failed. {message}") + raise SystemExit(ex.returncode) + return + + else: + raise NotImplementedError(f'Runtime for platform "{platform}" not supported yet') + + if sys.platform != "linux": + raise NotImplementedError(f"Unable to launch Linux systems on non-Linux machines yet, " + f"please use the Vagrant setup") + + # Acquire filesystem image. # TODO: Add more advanced image registry, maybe using `docker-py`, # resolving image names from postroj-internal images, Docker Hub, # GHCR, etc. diff --git a/setup.py b/setup.py index 64668f0..ea9e1a1 100644 --- a/setup.py +++ b/setup.py @@ -76,6 +76,7 @@ ], }, install_requires=[ + "appdirs>=1,<2", "click>=7,<9", "furl>=2,<3", "subprocess-tee>=0.3,<1", diff --git a/testing/racker/test_run_windows.py b/testing/racker/test_run_windows.py new file mode 100644 index 0000000..8bafa13 --- /dev/null +++ b/testing/racker/test_run_windows.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- +# (c) 2022 Andreas Motl +import os +import subprocess +import sys +from pathlib import Path +import shlex +import socket +from subprocess import CalledProcessError, CompletedProcess + +import pytest + + +if "rackerhost-debian11" in socket.gethostname(): + pytest.skip("Nested virtualization with VT-x fails on developer's macOS workstation", allow_module_level=True) + + +def run_racker(command: str) -> subprocess.CompletedProcess: + program_path = Path(sys.argv[0]).parent + racker = program_path / "racker" + command = f"{racker} {command}" + process = subprocess.run(shlex.split(command), stdout=subprocess.PIPE) + process.check_returncode() + return process + + +def test_run_windows_cmd_success(): + """ + Launch a Windows Nanoserver container and invoke a `cmd` command. + """ + process = run_racker("--verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows/nanoserver:1809-amd64 cmd /C 'echo Hello, world.'") + process.check_returncode() + assert process.stdout == b"Hello, world.\r\n" + + +def test_run_windows_cmd_failure(): + """ + Check exit code propagation of a failing `cmd` command. + """ + with pytest.raises(CalledProcessError) as ex: + run_racker("--verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows/nanoserver:1809-amd64 cmd /C 'exit 66'") + process: CompletedProcess = ex.value + assert process.returncode == 66 + + +def test_run_windows_powershell_success(): + """ + Launch a Windows Server Core container and invoke a PowerShell command. + """ + process = run_racker("--verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2019-amd64 -- 'powershell -Command {echo \"Hello, world.\"}'") + process.check_returncode() + # FIXME: Why does `echo` get echoed here!? + assert process.stdout == b"echo Hello, world.\r\n" + + +def test_run_windows_powershell_failure(): + """ + Check exit code propagation of a failing PowerShell command. + """ + with pytest.raises(CalledProcessError) as ex: + run_racker("--verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2019-amd64 -- 'powershell -Command exit 66'") + process: CompletedProcess = ex.value + assert process.returncode == 66 + + +def test_run_windows_bash_success(): + """ + Launch a Windows Server Core container and invoke a Bash command. + """ + process = run_racker("--verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2019-amd64 -- 'sh -c \"echo Hello, world.\"'") + process.check_returncode() + assert process.stdout == b"Hello, world.\n" + + +def test_run_windows_bash_failure(): + """ + Check exit code propagation of a failing Bash command. + """ + with pytest.raises(CalledProcessError) as ex: + run_racker("--verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2019-amd64 -- 'sh -c \"exit 66\"'") + process: CompletedProcess = ex.value + assert process.returncode == 66 From 5a47419d706de59cac69253155c79d38939c1cf4 Mon Sep 17 00:00:00 2001 From: Andreas Motl Date: Wed, 8 Jun 2022 02:44:48 +0200 Subject: [PATCH 02/14] [windows] CI: Don't run Windows tests on GHA, it takes too much disk space The GHA job output already croaked about this, like: You are running out of disk space. The runner will stop working when the machine runs out of disk space. Free space left: 0 MB --- .github/workflows/tests.yaml | 4 ++-- testing/racker/test_run_windows.py | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 698298c..ede719d 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -49,8 +49,8 @@ jobs: - name: Run tests # FIXME: Can this be run without elevated privileges? run: | - sudo $(command -v pytest) - coverage xml + sudo --preserve-env $(command -v pytest) + coverage xml --omit postroj/winrunner.py - name: Upload coverage results to Codecov uses: codecov/codecov-action@v3 diff --git a/testing/racker/test_run_windows.py b/testing/racker/test_run_windows.py index 8bafa13..f3a09d0 100644 --- a/testing/racker/test_run_windows.py +++ b/testing/racker/test_run_windows.py @@ -14,6 +14,10 @@ if "rackerhost-debian11" in socket.gethostname(): pytest.skip("Nested virtualization with VT-x fails on developer's macOS workstation", allow_module_level=True) +if "GITHUB_ACTIONS" in os.environ: + pytest.skip("Installing the Vagrant filesystem image for Windows " + "takes too much disk space on GitHub Actions", allow_module_level=True) + def run_racker(command: str) -> subprocess.CompletedProcess: program_path = Path(sys.argv[0]).parent From 2e9393aa80d917efb71041abb028bce0a376596e Mon Sep 17 00:00:00 2001 From: Andreas Motl Date: Thu, 9 Jun 2022 00:51:34 +0200 Subject: [PATCH 03/14] [windows] Add support for Windows Server Core 2016, 2022 and friends - windows/servercore:ltsc2016 - windows/servercore:ltsc2022 - windows/nanoserver:ltsc2022 --- CHANGES.rst | 4 ++- README.rst | 6 ++++ postroj/exceptions.py | 2 +- postroj/winrunner.py | 72 +++++++++++++++++++++++++++++++++++++++---- racker/cli.py | 7 +++-- 5 files changed, 81 insertions(+), 10 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index ecc3de5..422302c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -15,7 +15,9 @@ in progress - Add support for Windows Server Core 2019 and friends, like ``windows/servercore:ltsc2019``, ``windows/nanoserver:1809``, or ``eclipse-temurin:17-jdk``. - +- Add support for Windows Server Core 2016, 2022 and friends, like + ``windows/servercore:ltsc2016``, ``windows/servercore:ltsc2022``, or + ``windows/nanoserver:ltsc2022``. 2022-05-20 0.2.0 ================ diff --git a/README.rst b/README.rst index 45d0f0d..480ce82 100644 --- a/README.rst +++ b/README.rst @@ -263,7 +263,9 @@ Windows examples:: # Nanoserver: 250 MB, Servercore: 6 GB, Servercore with Java: 7 GB, Windows: 15 GB # Launch an interactive command prompt (cmd, PowerShell or Bash). + racker --verbose run -it --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2016-amd64 cmd racker --verbose run -it --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2019-amd64 powershell + racker --verbose run -it --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2022-amd64 bash # Launch a single command. racker --verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2019-amd64 -- 'powershell -Command {echo "Hello, world."}' @@ -279,6 +281,7 @@ Windows examples:: # Invoke a Java command prompt (JShell) with different Java versions. racker run -it --rm --platform=windows/amd64 openjdk:18-windowsservercore-1809 jshell + racker run -it --rm --platform=windows/amd64 openjdk:19-windowsservercore-ltsc2022 jshell racker run -it --rm --platform=windows/amd64 eclipse-temurin:18-jdk jshell System.out.println("OS: " + System.getProperty("os.name") + ", version " + System.getProperty("os.version")) System.out.println("Java: " + System.getProperty("java.vendor") + ", version " + System.getProperty("java.version")) @@ -287,9 +290,12 @@ Windows examples:: # Windows Nano Server. racker --verbose run -it --rm --platform=windows/amd64 mcr.microsoft.com/windows/nanoserver:1809-amd64 cmd racker --verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows/nanoserver:1809-amd64 cmd /C echo Hello, world. + racker --verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows/nanoserver:ltsc2022-amd64 cmd /C echo Hello, world. + racker --verbose run -it --rm --platform=windows/amd64 mcr.microsoft.com/powershell:nanoserver-ltsc2022 pwsh # Full Windows. racker --verbose run -it --rm --platform=windows/amd64 mcr.microsoft.com/windows:1809-amd64 cmd + racker --verbose run -it --rm --platform=windows/amd64 mcr.microsoft.com/windows:ltsc2022-amd64 cmd diff --git a/postroj/exceptions.py b/postroj/exceptions.py index 20a7a2b..bfbd69b 100644 --- a/postroj/exceptions.py +++ b/postroj/exceptions.py @@ -11,7 +11,7 @@ class ProvisioningError(Exception): class InvalidImageReference(Exception): - pass + returncode = 1 class InvalidPhysicalImage(Exception): diff --git a/postroj/winrunner.py b/postroj/winrunner.py index 1bfe762..7cbf279 100644 --- a/postroj/winrunner.py +++ b/postroj/winrunner.py @@ -16,14 +16,14 @@ import appdirs import pkg_resources -from postroj.util import port_is_up, cmd, fix_tty +from postroj.exceptions import InvalidImageReference +from postroj.util import port_is_up, cmd, fix_tty, subprocess_get_error_message logger = logging.getLogger(__name__) class WinRunner: - BOX = "2019-box" VCPUS = os.environ.get("RACKER_VM_VCPUS", 6) MEMORY = os.environ.get("RACKER_VM_MEMORY", 6144) @@ -31,6 +31,10 @@ def __init__(self, image: str): self.image_base = image self.image_real = "racker-runtime/" + self.image_base.replace("/", "-") + self.BOX = None + self.choose_box() + logger.info(f"Using host machine box image {self.BOX} for launching container image {self.image_base}") + self.workdir = Path(appdirs.user_state_dir(appname="racker")) self.workdir.mkdir(exist_ok=True, parents=True) @@ -44,9 +48,8 @@ def setup(self): logger.info(f"Installing Windows Docker Machine into {self.wdmdir}") command = f""" cd '{self.workdir}' - git clone https://github.com/StefanScherer/windows-docker-machine + git clone https://github.com/cicerops/windows-docker-machine --branch racker cd windows-docker-machine - #ls -alF sed -i 's/v.cpus = [0-9]\+/v.cpus = {self.VCPUS}/' Vagrantfile sed -i 's/v.memory = [0-9]\+/v.memory = {self.MEMORY}/' Vagrantfile sed -i 's/v.maxmemory = [0-9]\+/v.maxmemory = {self.MEMORY}/' Vagrantfile @@ -55,7 +58,60 @@ def setup(self): else: logger.info(f"Windows Docker Machine already installed into {self.wdmdir}") - def start(self): + def choose_box(self): + """ + Choose the right box based on the container image to launch. + """ + + image = self.image_base + if not image.startswith("docker://"): + image = f"docker://{image}" + + logger.info(f"Inquiring information about OCI image '{image}'") + + if "RACKER_VM_BOX" in os.environ: + self.BOX = os.environ["RACKER_VM_BOX"] + return + + # TODO: Cache the response from `skopeo inspect` to avoid the 3-second speed bump. + # https://github.com/cicerops/racker/issues/6 + command = f"skopeo --override-os=windows inspect --config --raw {image}" + try: + process = cmd(command, capture=True) + except subprocess.CalledProcessError as ex: + message = subprocess_get_error_message(exception=ex) + message = f"Inquiring information about OCI image '{image}' failed. {message}" + logger.error(message) + exception = InvalidImageReference(message) + exception.returncode = ex.returncode + raise exception + + image_info = json.loads(process.stdout) + image_os_name = image_info["os"] + image_os_version = image_info["os.version"] + logger.info(f"Image inquiry said os={image_os_name}, version={image_os_version}") + + if image_os_name != "windows": + raise ValueError(f"Container image {image} is not Windows, but {image_os_name} instead") + + # https://docs.microsoft.com/en-us/virtualization/windowscontainers/deploy-containers/version-compatibility + os_version_box_map = { + "10.0.14393": "2016-box", + "10.0.16299": "2019-box", # openjdk:8-windowsservercore-1709 + "10.0.17763": "2019-box", + "10.0.19042": "2022-box", + "10.0.20348": "2022-box", + } + + for os_version, box in os_version_box_map.items(): + if image_os_version.startswith(os_version): + self.BOX = box + + if self.BOX is None: + raise ValueError(f"Unable to choose host machine box image for container image {image}, matching OS version {image_os_version}. " + f"Please report this error to https://github.com/cicerops/racker/issues/new.") + + def start(self, provision=False): """ Start the "Windows Docker Machine" virtual machine. @@ -81,7 +137,11 @@ def start(self): # https://github.com/moby/moby/blob/0e04b514fb/integration-cli/docker_cli_run_test.go cmd(f"docker --context={self.BOX} ps", capture=True) - if "nanoserver" in self.image_base: + # Skip installing software using Chocolatey for specific Windows OS versions. + # - Windows Nanoserver does not have PowerShell. + # - Windows 2016 croaks like: + # `The command 'cmd /S /C choco install --yes ...' returned a non-zero code: 3221225785` + if "nanoserver" in self.image_base or self.BOX == "2016-box": self.image_real = self.image_base else: self.provision_image() diff --git a/racker/cli.py b/racker/cli.py index cf55535..d811c84 100644 --- a/racker/cli.py +++ b/racker/cli.py @@ -10,7 +10,7 @@ from postroj.api import pull_curated_image from postroj.container import PostrojContainer -from postroj.exceptions import ProvisioningError +from postroj.exceptions import ProvisioningError, InvalidImageReference from postroj.util import boot, subprocess_get_error_message from postroj.winrunner import WinRunner from racker.image import ImageLibrary @@ -93,7 +93,10 @@ def racker_run(ctx, interactive: bool, tty: bool, rm: bool, platform: str, image elif platform == "windows/amd64": logger.info(f"Preparing runtime environment for platform {platform} and image {image}") - runner = WinRunner(image=image) + try: + runner = WinRunner(image=image) + except InvalidImageReference as ex: + raise SystemExit(ex.returncode) try: with redirect_stdout(sys.stderr): with redirect_stderr(sys.stderr): From 43e896deff90b1cd1093a0fd6f21e805efd3df15 Mon Sep 17 00:00:00 2001 From: Andreas Motl Date: Sat, 11 Jun 2022 01:36:26 +0200 Subject: [PATCH 04/14] [windows] Improve `winrunner.Dockerfile` - Don't manipulate $PATH. To make `git` available on the program search path, use the package parameter `/GitAndUnixToolsOnPath` instead. - Rename native Windows programs like `curl.exe` and `convert.exe` to reduce ambiguity with their FOSS/GNU resp. Chocolatey-installed counterparts, also without needing to manipulate $PATH. - Also install `busybox` and `nano`. --- postroj/winrunner.Dockerfile | 24 ++++++++++++++---------- postroj/winrunner.py | 3 ++- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/postroj/winrunner.Dockerfile b/postroj/winrunner.Dockerfile index a813e23..05da7b3 100644 --- a/postroj/winrunner.Dockerfile +++ b/postroj/winrunner.Dockerfile @@ -1,6 +1,11 @@ -# Provision a Windows operating system image with additional software. -# Automatically installs the open source version of the Chocolatey package manager. -# By default, it installs `git`, `curl`, and `wget`. +# Windows runner Dockerfile for Racker +# https://github.com/cicerops/racker +# +# Provision a Windows operating system image. +# +# - Install the FOSS version of the Chocolatey package manager. +# - Install additional software using Chocolatey. +# ARG BASE_IMAGE @@ -10,11 +15,10 @@ FROM ${BASE_IMAGE} RUN powershell -Command Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')) # Install essential and convenience programs. -# TODO: Verify that the right `curl` program has priority within the program search path. -RUN choco install --yes git curl wget +RUN choco install --yes busybox curl nano wget +RUN choco install --yes git --package-parameters="'/GitAndUnixToolsOnPath /Editor:Nano'" -# Make `bash` (from Git Bash, MINGW64) available on the program search path. -RUN powershell ([Environment]::SetEnvironmentVariable('Path', $env:Path + ';' + $($env:SystemDrive) + '\Program Files\Git\bin', 'Machine')) - -# Display the program search path. -RUN powershell echo \"(Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' -Name Path).Path\" +# Rename Windows-native programs in favor of Chocolatey-installed/FOSS/GNU ones. +# An alternative would be to manipulate `$PATH`, but that is more tedious. +RUN sh -c 'test -f /c/Windows/system32/curl && mv /c/Windows/system32/curl /c/Windows/system32/curl-win' +RUN sh -c 'test -f /c/Windows/system32/convert && mv /c/Windows/system32/convert /c/Windows/system32/convert-ntfs' diff --git a/postroj/winrunner.py b/postroj/winrunner.py index 7cbf279..ac83211 100644 --- a/postroj/winrunner.py +++ b/postroj/winrunner.py @@ -152,7 +152,8 @@ def provision_image(self): - Provision a Windows operating system image with additional software. - Automatically installs the open source version of the Chocolatey package manager. - - By default, it installs `git`, `curl`, and `wget`. + - By default, it automatically installs some programs like `busybox`, `curl`, `git`, + `nano`, and `wget`. """ logger.info(f"Provisioning Docker image for Windows environment based on {self.image_base}") From 8a5555d73963d545b53dc81db6d3011a82c026f3 Mon Sep 17 00:00:00 2001 From: Andreas Motl Date: Sat, 11 Jun 2022 01:38:56 +0200 Subject: [PATCH 05/14] [windows] Add optional `--provision` option to `vagrant up` invocation --- postroj/winrunner.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/postroj/winrunner.py b/postroj/winrunner.py index ac83211..a60550f 100644 --- a/postroj/winrunner.py +++ b/postroj/winrunner.py @@ -124,8 +124,12 @@ def start(self, provision=False): logger.info("Docker context is online") else: logger.info("Docker context is offline, starting VirtualBox VM with Vagrant") - cmd(f"vagrant provision {self.BOX}", cwd=self.wdmdir, use_stderr=True) - cmd(f"vagrant up --provider=virtualbox {self.BOX}", cwd=self.wdmdir, use_stderr=True) + # TODO: The `provision` option flag is not wired in any way yet. + # https://github.com/cicerops/racker/issues/7 + provision_option = "" + if provision: + provision_option = "--provision" + cmd(f"vagrant up --provider=virtualbox {provision_option} {self.BOX}", cwd=self.wdmdir, use_stderr=True) logger.info("Pinging Docker context") if not self.docker_context_online(): From f49c209658ca28a3ca2398a11e8e0d9ec7e6c771 Mon Sep 17 00:00:00 2001 From: Andreas Motl Date: Sat, 11 Jun 2022 23:13:32 +0200 Subject: [PATCH 06/14] [windows] Improve documentation about the Windows backend --- CHANGES.rst | 1 + README.rst | 51 ++---- doc/cratedb.rst | 36 +--- doc/winrunner.rst | 451 ++++++++++++++++++++++++++++++++++++++++------ 4 files changed, 417 insertions(+), 122 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 422302c..30c2f25 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -18,6 +18,7 @@ in progress - Add support for Windows Server Core 2016, 2022 and friends, like ``windows/servercore:ltsc2016``, ``windows/servercore:ltsc2022``, or ``windows/nanoserver:ltsc2022``. +- Improve documentation about the Windows backend 2022-05-20 0.2.0 ================ diff --git a/README.rst b/README.rst index 480ce82..0868172 100644 --- a/README.rst +++ b/README.rst @@ -224,7 +224,9 @@ Racker The ``racker`` program aims to resemble the semantics of Docker by providing a command line interface compatible with the ``docker`` command. -Linux examples:: +Linux +----- +:: # Invoke the vanilla Docker `hello-world` image. # FIXME: Does not work yet. @@ -256,47 +258,19 @@ Linux examples:: time echo "hello world" | racker run -it --rm fedora:37 cat /dev/stdin > hello cat hello -Windows examples:: - - # Windows OS images, mostly LTSC (Long-Term Servicing Channel). - # Please note the download sizes. - # Nanoserver: 250 MB, Servercore: 6 GB, Servercore with Java: 7 GB, Windows: 15 GB - - # Launch an interactive command prompt (cmd, PowerShell or Bash). - racker --verbose run -it --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2016-amd64 cmd - racker --verbose run -it --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2019-amd64 powershell - racker --verbose run -it --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2022-amd64 bash - - # Launch a single command. - racker --verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2019-amd64 -- 'powershell -Command {echo "Hello, world."}' - racker --verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2019-amd64 'sh -c "echo Hello, world."' - - # Inquire system information. - racker run --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2019-amd64 wmic os get caption - racker run --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2019-amd64 'powershell -Command Get-ComputerInfo' - - # Use stdin and stdout, with time keeping. - time racker --verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows/nanoserver:1809-amd64 cmd /C echo "Hello, world." > hello - cat hello +Windows +------- - # Invoke a Java command prompt (JShell) with different Java versions. - racker run -it --rm --platform=windows/amd64 openjdk:18-windowsservercore-1809 jshell - racker run -it --rm --platform=windows/amd64 openjdk:19-windowsservercore-ltsc2022 jshell - racker run -it --rm --platform=windows/amd64 eclipse-temurin:18-jdk jshell - System.out.println("OS: " + System.getProperty("os.name") + ", version " + System.getProperty("os.version")) - System.out.println("Java: " + System.getProperty("java.vendor") + ", version " + System.getProperty("java.version")) - /exit +An example of a basic command line invocation should get you started, +especially if you are familiar with the ``docker`` command:: - # Windows Nano Server. - racker --verbose run -it --rm --platform=windows/amd64 mcr.microsoft.com/windows/nanoserver:1809-amd64 cmd - racker --verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows/nanoserver:1809-amd64 cmd /C echo Hello, world. - racker --verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows/nanoserver:ltsc2022-amd64 cmd /C echo Hello, world. - racker --verbose run -it --rm --platform=windows/amd64 mcr.microsoft.com/powershell:nanoserver-ltsc2022 pwsh + racker --verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2022 -- wmic os get caption - # Full Windows. - racker --verbose run -it --rm --platform=windows/amd64 mcr.microsoft.com/windows:1809-amd64 cmd - racker --verbose run -it --rm --platform=windows/amd64 mcr.microsoft.com/windows:ltsc2022-amd64 cmd + Caption + Microsoft Windows Server 2022 Datacenter +More extensive information, including many examples, can be found at the +`Racker Windows backend`_ documentation. Postroj @@ -483,6 +457,7 @@ Troubleshooting .. _Packer: https://www.packer.io/ .. _Podman: https://podman.io/ .. _Racker sandbox installation: https://github.com/cicerops/racker/blob/main/doc/sandbox.rst +.. _Racker Windows backend: https://github.com/cicerops/racker/blob/main/doc/winrunner.rst .. _skopeo: https://github.com/containers/skopeo .. _systemd: https://www.freedesktop.org/wiki/Software/systemd/ .. _systemd-nspawn: https://www.freedesktop.org/software/systemd/man/systemd-nspawn.html diff --git a/doc/cratedb.rst b/doc/cratedb.rst index 8c88ce3..3c5e8cb 100644 --- a/doc/cratedb.rst +++ b/doc/cratedb.rst @@ -2,47 +2,27 @@ Using Racker and Postroj for CrateDB CI ####################################### -.. note:: - - This is still a work in progress. - - -************* -Prerequisites -************* - -When creating the "Windows Docker Machine" virtual machine, the program will -configure it to use 6 VPUs and 6144 MB system memory. - -In order to adjust those values, use these environment variables before -invoking the later commands:: - - export RACKER_VM_VCPUS=12 - export RACKER_VM_MEMORY=8192 - -If you want to adjust the values after the initial deployment, you will have to -reset the "Windows Docker Machine" installation directory. For example, it is: - -- On Linux: ``/root/.local/state/racker/windows-docker-machine`` -- On macOS: ``/Users/amo/Library/Application Support/racker/windows-docker-machine`` - - ********** racker run ********** Purpose: Invoke programs in a Java/OpenJDK environment, within a -virtualized/dockerized Windows installation. +virtualized/dockerized, volatile/ephemeral Windows environment. Run the CrateDB test suite on OpenJDK 18 (Eclipse Temurin):: - time racker run --rm --platform=windows/amd64 eclipse-temurin:18-jdk \ + time racker --verbose run --rm --platform=windows/amd64 eclipse-temurin:18-jdk \ "sh -c 'mkdir /c/src; cd /c/src; git clone https://github.com/crate/crate --depth=1; cd crate; ./gradlew --no-daemon --parallel -PtestForks=2 :server:test -Dtests.crate.run-windows-incompatible=false --stacktrace'" +Use the same image, but select a specific operating system version:: + + export RACKER_VM_BOX=2019-box + racker --verbose run --rm --platform=windows/amd64 eclipse-temurin:18-jdk -- wmic os get caption + Invoke a Java command prompt (JShell) with OpenJDK 18:: - racker run -it --rm --platform=windows/amd64 eclipse-temurin:18-jdk jshell + racker --verbose run -it --rm --platform=windows/amd64 eclipse-temurin:18-jdk jshell System.out.println("OS: " + System.getProperty("os.name") + ", version " + System.getProperty("os.version")) System.out.println("Java: " + System.getProperty("java.vendor") + ", version " + System.getProperty("java.version")) diff --git a/doc/winrunner.rst b/doc/winrunner.rst index 97110fc..238be66 100644 --- a/doc/winrunner.rst +++ b/doc/winrunner.rst @@ -1,112 +1,451 @@ -############## -Windows runner -############## - -.. note:: - - This is still a work in progress. +###################### +Racker Windows backend +###################### ***** About ***** -Launch an interactive command prompt (cmd, PowerShell or Bash) within a Windows -environment or invoke programs non-interactively. +Launch an interactive command prompt (cmd, PowerShell, or Bash) within a +Windows environment (2016, 2019, or 2022) or invoke programs +non-interactively. -References -========== +Features +======== -- https://github.com/PyTables/PyTables/pull/872#issuecomment-773535041 +- The subsystem is heavily based on the excellent `Windows Docker Machine`_. +- The package manager `Chocolatey`_ is pre-installed on the container images + where PowerShell is available. +- Programs like ``busybox``, ``curl``, ``git``, ``nano``, and ``wget`` are + pre-installed on the container images where `Chocolatey`_ is available. +- The `Windows container version compatibility`_ problem is conveniently + solved by automatically selecting the right machine matching the requested + container image. -***** -Setup -***** -:: +Use cases +========= - # Install VirtualBox, Vagrant, and Docker. - brew install virtualbox vagrant docker +The first encounter with `Windows Docker Machine`_ was when aiming to run the +build process of the PyTables Python package within a Windows environment, see +`Wheels for Windows`_. - # Install Windows VM with Docker environment. - git clone https://github.com/StefanScherer/windows-docker-machine - cd windows-docker-machine +The second use case was to run the Java test suite of CrateDB within a Windows +environment, see `Using Racker and Postroj for CrateDB CI`_. - # Adjust resources. - sed -i 's/v.memory = 2048/v.memory = 8192/' Vagrantfile - sed -i 's/v.cpus = 2/v.cpus = 8/' Vagrantfile - # Run the box. - vagrant up --provider virtualbox 2019-box ***** -Usage +Setup ***** +:: -Basic usage:: + # Install VirtualBox, Vagrant, Docker, Python, and Racker. + brew install virtualbox vagrant docker python + pip install racker - racker run --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2019-amd64 \ - cmd /C echo "Hello, world." -Install software packages using `Chocolatey`_:: +******** +Synopsis +******** +:: - racker run -it --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2019-amd64 \ - powershell - Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')) - choco install --yes git - Install-ChocolateyPath -PathToInstall "$($env:SystemDrive)\Program Files\Git\bin" + racker --verbose run --rm \ + --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2019 -- \ + wmic os get caption ************* -Miscellaneous +Configuration ************* -Manipulating ``PATH`` +Resources +========= + +When creating the `Windows Docker Machine`_ virtual machine, the program will +configure it to use 6 VCPUs and 6144 MB system memory by default. + +In order to adjust those values, use these environment variables before +invoking the later commands:: + + export RACKER_VM_VCPUS=12 + export RACKER_VM_MEMORY=8192 + +If you want to adjust the values after the initial deployment, you will have to +reset the `Windows Docker Machine`_ installation directory. For example, it is: + +- On Linux: ``/root/.local/state/racker/windows-docker-machine`` +- On macOS: ``/Users/amo/Library/Application Support/racker/windows-docker-machine`` + +Runtime +======= + +The architecture of Windows leads to container compatibility requirements that +are different than on Linux, more background about this detail can be found at +`Windows container version compatibility`_. + +In order to provide appropriate convenience, Racker's launcher subsystem +inquires the ``os.version`` attribute of the OCI image about the designated +version of Windows version before starting the container. Based on the version, +the corresponding Windows Docker Machine host is selected to run the payload +on. Currently, the supported operating systems are Windows 2016, 2019, and 2022. + +Certain container images can still be launched on mismatching operating system +versions, for example, the `eclipse-temurin`_ container images. By default, +when possible, the image will be launched on a Windows 2022 machine. If you +want to explicitly control on which runner host the container will be launched, +use another environment variable:: + + export RACKER_VM_BOX=2019-box + +If you receive error messages like ``docker: no matching manifest for +windows/amd64 10.0.17763 in the manifest list entries.``, reset this setting +by typing:: + + unset RACKER_VM_BOX + +Vagrant stores the boxes in this directory: + +- On Linux an macOS: ``~/.vagrant.d/boxes`` +- On Windows: ``C:/Users/USERNAME/.vagrant.d/boxes`` + +The sizes of the three Vagrant boxes are: + +- ``StefanScherer/windows_2016_docker``: 11.0 GB +- ``StefanScherer/windows_2019_docker``: 14.0 GB +- ``StefanScherer/windows_2022_docker``: 6.4 GB + + +******** +Examples +******** + + +Introduction +============ + +For understanding some of the acronyms used in the following section, it is +good to memorize those: + +- LTSC: Long-Term Servicing Channel +- SAC: Semi-Annual Channel + +For more details, see `Overview of System Center release options`_. + + +Choosing a base image ===================== -Display the content of the ``PATH`` environment variable:: +Quoting from `Windows Container Base Images`_: - echo %PATH% - (Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' -Name Path).Path + How do you choose the right base image to build upon? For most users, Windows + Server Core and Nanoserver will be the most appropriate image to use. Each + base image is briefly described below: -Set the content of the ``PATH`` environment variable:: + - ``Nano Server`` is an ultralight Windows offering for new application + development. + - ``Server Core`` is medium in size and a good option for "lifting and + shifting" Windows Server apps. + - ``Windows Server`` has full Windows API support, and allows you to use + more server features. + - ``Windows`` is the largest image and has full Windows API support for + workloads. - setx PATH "$env:path;$($env:SystemDrive)\Program Files\Git\bin" -m - [Environment]::SetEnvironmentVariable("Path", $env:Path + ";$($env:SystemDrive)\Program Files\Git\bin", "Machine") +The examples outlined within this section will use different Windows container +images. According to the feature set outlined above, their download sizes are +different. + +- Nano Server: 125 MB +- Server Core: 2.2 GB +- Windows Server: 4.8 GB +- Windows: 7.1 GB + + +System information +================== + +Install and run `Winfetch`_:: + + racker --verbose run --rm \ + --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2022 -- \ + cmd /C 'choco install --yes --force winfetch & refreshenv & winfetch' + +.. figure:: https://user-images.githubusercontent.com/453543/173195228-b75c8727-7187-4c38-ae28-f74098dfb450.png + :width: 800 + +With ``ver``, ``reg``, WMI and PowerShell:: + + # Both ``ver`` and ``reg`` will be available even on Nano Server. + racker --verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2016 -- cmd /C ver + racker --verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2016 -- 'reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion" /v ProductName' + racker --verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2016 -- 'reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion" /v InstallationType' + + # WMI and PowerShell are not always available. + racker --verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2016 -- wmic os get caption + racker --verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2019 -- powershell -Command Get-ComputerInfo + racker --verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2022 -- powershell -Command Get-ComputerInfo -Property WindowsProductName + +With ``busybox``:: + + racker --verbose run -it --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2022 -- cmd + + C:\>busybox nproc + 6 + + C:\>busybox free -m + total used free shared buff/cache available + Mem: 2048 1422 16774251 0 3591 0 + Swap: 1664 0 1664 + C:\>busybox df -h + Filesystem Size Used Available Use% Mounted on + C: 19.9G 83.3M 19.8G 0% C:/ -Which shell spawns faster? + +Interactive command prompt ========================== + +Where possible, the operating system images offer three terminal/shell +programs: cmd, PowerShell, and Bash. To get an interactive shell, run:: + + racker --verbose run -it --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2016 cmd + racker --verbose run -it --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2019 powershell + racker --verbose run -it --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2022 bash + + +Invoke single command +===================== :: - time docker --context=2019-box run -it --rm openjdk:17-windowsservercore-1809 cmd /C "echo Hello, world." + # Run a basic command with cmd, PowerShell, and Bash. + racker --verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2016 cmd /C echo "Hello, world." + racker --verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2019 -- 'powershell -Command {echo "Hello, world."}' + racker --verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2022 'sh -c "echo Hello, world."' + + # Use stdin and stdout, with time keeping. + time racker --verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows/nanoserver:1809 cmd /C echo "Hello, world." > hello + cat hello + +Nano Server +=========== :: - time docker --context=2019-box run -it --rm openjdk:17-windowsservercore-1809 powershell -Command "echo 'Hello, world.'" + # Display system version. + racker --verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows/nanoserver:sac2016 cmd /C ver + racker --verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows/nanoserver:1809 cmd /C ver + racker --verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows/nanoserver:ltsc2022 cmd /C ver + # Interactive shell with cmd. + racker --verbose run -it --rm --platform=windows/amd64 mcr.microsoft.com/windows/nanoserver:1809 cmd + # Interactive shell with PowerShell. + racker --verbose run -it --rm --platform=windows/amd64 mcr.microsoft.com/powershell:nanoserver-ltsc2022 pwsh -*********** -Admin guide -*********** +Windows Server +============== :: - docker --context=2019-box run -it --rm openjdk:17-windowsservercore-1809 cmd + racker --verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows/server:ltsc2022 -- cmd /C ver + racker --verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows/server:ltsc2022 -- wmic os get caption + racker --verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows/server:ltsc2022 -- powershell -Command Get-ComputerInfo -Property WindowsProductName + + +Windows +======= +:: + + # Windows 10 + racker --verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows:1809 -- cmd /C ver + racker --verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows:1809 -- wmic os get caption + racker --verbose run --rm --platform=windows/amd64 mcr.microsoft.com/windows:1809 -- powershell -Command Get-ComputerInfo -Property WindowsProductName + + # Untested. + racker --verbose run -it --rm --platform=windows/amd64 mcr.microsoft.com/windows:20H2 wmic os get caption + + +Midnight Commander +================== + +Install and run `Midnight Commander`_:: + + racker --verbose run -it --rm \ + --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2022 -- \ + cmd /C 'choco install --yes --force mc --install-arguments=/tasks=modifypath & refreshenv & mc' + +.. figure:: https://user-images.githubusercontent.com/453543/173195789-9ef87618-5526-4317-99d7-b0dee6ca3970.png + :width: 800 + + +Python +====== + +Select a Windows container image including `Python`_ and launch it. + +Display Python version, launched within containers in different environments:: + + # Server Core + racker --verbose run --rm --platform=windows/amd64 python:2.7 -- python -V + racker --verbose run --rm --platform=windows/amd64 python:3.9 -- python -V + racker run --rm --platform=windows/amd64 winamd64/python:3.9-windowsservercore-1809 -- python -V + racker run --rm --platform=windows/amd64 winamd64/python:3.10-windowsservercore-ltsc2022 -- python -V + racker run --rm --platform=windows/amd64 winamd64/python:3.11-rc -- python -V + + # Explicitly select `2019-box` as different host OS. + # The default would be to automatically select `2022-box`. + RACKER_VM_BOX=2019-box racker --verbose run --rm --platform=windows/amd64 winamd64/python:3.11-rc -- python -V + + # Nano Server + racker --verbose run --rm --platform=windows/amd64 stefanscherer/python-windows:nano -- python -V + +Display the Zen of Python:: + + racker --verbose run --rm --platform=windows/amd64 python:3.9 -- 'python -c "import this"' + +Install NumPy and display its configuration:: + + racker --verbose run --rm --platform=windows/amd64 python:3.10 -- 'sh -c "pip install numpy; python -c \"import numpy; numpy.show_config()\""' + + +Java +==== + +Display Java version, launched within containers in different environments:: + + # Eclipse Temurin. + racker --verbose run --rm --platform=windows/amd64 eclipse-temurin:16-jdk -- java --version + racker --verbose run --rm --platform=windows/amd64 eclipse-temurin:18-jdk -- java --version + + # Oracle OpenJDK. + racker --verbose run --rm --platform=windows/amd64 openjdk:8 -- java -version + racker --verbose run --rm --platform=windows/amd64 openjdk:8-windowsservercore-ltsc2016 -- java -version + racker --verbose run --rm --platform=windows/amd64 openjdk:8-windowsservercore-1809 -- java -version + racker --verbose run --rm --platform=windows/amd64 openjdk:19 -- java --version + + # Explicitly select `2019-box` as different host OS. + # The default would be to automatically select `2022-box`. + RACKER_VM_BOX=2019-box racker --verbose run --rm --platform=windows/amd64 openjdk:19 -- java --version + + # Nano Server + racker --verbose run --rm --platform=windows/amd64 openjdk:19-nanoserver -- java --version + + +Invoke a Java command prompt (JShell) with different Java and OS versions:: + + racker --verbose run -it --rm --platform=windows/amd64 eclipse-temurin:18-jdk jshell + racker --verbose run -it --rm --platform=windows/amd64 openjdk:8-windowsservercore-ltsc2016 jshell + racker --verbose run -it --rm --platform=windows/amd64 openjdk:8-windowsservercore-1809 jshell + racker --verbose run -it --rm --platform=windows/amd64 openjdk:19-windowsservercore-ltsc2022 jshell + System.out.println("OS: " + System.getProperty("os.name") + ", version " + System.getProperty("os.version")) + System.out.println("Java: " + System.getProperty("java.vendor") + ", version " + System.getProperty("java.version")) + /exit + + + +****************** +Container handbook +****************** + +Inquire system information +========================== + +On systems where ``wmic`` is installed:: + + docker --context=2019-box run -it --rm mcr.microsoft.com/windows/servercore:ltsc2019 cmd wmic cpu get NumberOfCores wmic computersystem get TotalPhysicalMemory -:: +On systems where PowerShell is installed:: - docker --context=2019-box run -it --rm openjdk:17-windowsservercore-1809 powershell + docker --context=2019-box run -it --rm mcr.microsoft.com/windows/servercore:ltsc2019 powershell Get-ComputerInfo -Terminate an environment:: - docker --context=2019-box ps - docker --context=2019-box stop 5e2fe406ccbc +Manipulating ``PATH`` +===================== + +Display the content of the ``PATH`` environment variable:: + + echo %PATH% + (Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' -Name Path).Path + +Set the content of the ``PATH`` environment variable:: + + # Using `setx`. + setx PATH "$env:path;$($env:SystemDrive)\Program Files\Git\bin" -m + + # Using PowerShell. + [Environment]::SetEnvironmentVariable('Path', $env:Path + ';' + $($env:SystemDrive) + '\Program Files\Git\bin', 'Machine') + + + +*********** +Admin guide +*********** + + +Terminate a container +===================== + +You will experience situations where the invocation of programs will block your +terminal and you can't terminate the process using ``CTRL+C``. For example, try +to run ``wish.exe``. + +In such situations, you might want to kill the container. It works like this:: + + # Find the container id. + docker --context=2022-box ps + + # Terminate or stop the container. + docker --context=2022-box kill 08df5fc812f9 + docker --context=2022-box stop 08df5fc812f9 + + +The Docker contexts +=================== + +Communication from the Docker CLI to the Docker daemons running on the WDM +machines is established through Docker contexts. + +To list all active contexts, type:: + + docker context list + +To remove the contexts automatically established by WDM, type:: + + docker context rm 2016-box 2019-box 2022-box + + +Installing and using Chocolatey +=============================== + +The `Chocolatey`_ package manager can be used to install additional software like +``git`` and ``bash``:: + + racker run -it --rm --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2019 powershell + Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')) + + choco install --yes git --package-parameters="/GitAndUnixToolsOnPath /Editor:Nano" + iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/badrelmers/RefrEnv/main/refrenv.ps1')) + + $ bash --version + $ git --version + +The whole software catalog can be inquired at `Chocolatey community packages`_. .. _Chocolatey: https://chocolatey.org/ +.. _Chocolatey community packages: https://community.chocolatey.org/packages +.. _eclipse-temurin: https://hub.docker.com/_/eclipse-temurin +.. _Midnight Commander: https://en.wikipedia.org/wiki/Midnight_Commander +.. _Overview of System Center release options: https://docs.microsoft.com/en-us/system-center/ltsc-and-sac-overview +.. _Python: https://www.python.org/ +.. _Using Racker and Postroj for CrateDB CI: https://github.com/cicerops/racker/blob/main/doc/cratedb.rst +.. _Windows Container Base Images: https://docs.microsoft.com/en-us/virtualization/windowscontainers/manage-containers/container-base-images +.. _Windows container version compatibility: https://docs.microsoft.com/en-us/virtualization/windowscontainers/deploy-containers/version-compatibility +.. _Windows Docker Machine: https://github.com/StefanScherer/windows-docker-machine +.. _Winfetch: https://github.com/kiedtl/winfetch +.. _Wheels for Windows: https://github.com/PyTables/PyTables/pull/872#issuecomment-773535041 From 387dc2087769cdbfdb85150c4e47384d840b3822 Mon Sep 17 00:00:00 2001 From: Andreas Motl Date: Sat, 11 Jun 2022 23:45:59 +0200 Subject: [PATCH 07/14] [windows] Rename environment variables for controlling the WDM The new names are ``RACKER_WDM_VCPUS``, ``RACKER_WDM_MEMORY``, and ``RACKER_WDM_MACHINE``. WDM means "Windows Docker Machine". --- CHANGES.rst | 3 +++ doc/cratedb.rst | 2 +- doc/winrunner.rst | 8 ++++---- postroj/winrunner.py | 45 ++++++++++++++++++++++---------------------- 4 files changed, 31 insertions(+), 27 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 30c2f25..85b2391 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -19,6 +19,9 @@ in progress ``windows/servercore:ltsc2016``, ``windows/servercore:ltsc2022``, or ``windows/nanoserver:ltsc2022``. - Improve documentation about the Windows backend +- Rename environment variables used to control the Windows Docker Machine + subsystem. The new names are ``RACKER_WDM_VCPUS``, ``RACKER_WDM_MEMORY``, + and ``RACKER_WDM_MACHINE``. 2022-05-20 0.2.0 ================ diff --git a/doc/cratedb.rst b/doc/cratedb.rst index 3c5e8cb..da05a46 100644 --- a/doc/cratedb.rst +++ b/doc/cratedb.rst @@ -17,7 +17,7 @@ Run the CrateDB test suite on OpenJDK 18 (Eclipse Temurin):: Use the same image, but select a specific operating system version:: - export RACKER_VM_BOX=2019-box + export RACKER_WDM_MACHINE=2019-box racker --verbose run --rm --platform=windows/amd64 eclipse-temurin:18-jdk -- wmic os get caption Invoke a Java command prompt (JShell) with OpenJDK 18:: diff --git a/doc/winrunner.rst b/doc/winrunner.rst index 238be66..1fbcf4a 100644 --- a/doc/winrunner.rst +++ b/doc/winrunner.rst @@ -98,13 +98,13 @@ when possible, the image will be launched on a Windows 2022 machine. If you want to explicitly control on which runner host the container will be launched, use another environment variable:: - export RACKER_VM_BOX=2019-box + export RACKER_WDM_MACHINE=2019-box If you receive error messages like ``docker: no matching manifest for windows/amd64 10.0.17763 in the manifest list entries.``, reset this setting by typing:: - unset RACKER_VM_BOX + unset RACKER_WDM_MACHINE Vagrant stores the boxes in this directory: @@ -296,7 +296,7 @@ Display Python version, launched within containers in different environments:: # Explicitly select `2019-box` as different host OS. # The default would be to automatically select `2022-box`. - RACKER_VM_BOX=2019-box racker --verbose run --rm --platform=windows/amd64 winamd64/python:3.11-rc -- python -V + RACKER_WDM_MACHINE=2019-box racker --verbose run --rm --platform=windows/amd64 winamd64/python:3.11-rc -- python -V # Nano Server racker --verbose run --rm --platform=windows/amd64 stefanscherer/python-windows:nano -- python -V @@ -327,7 +327,7 @@ Display Java version, launched within containers in different environments:: # Explicitly select `2019-box` as different host OS. # The default would be to automatically select `2022-box`. - RACKER_VM_BOX=2019-box racker --verbose run --rm --platform=windows/amd64 openjdk:19 -- java --version + RACKER_WDM_MACHINE=2019-box racker --verbose run --rm --platform=windows/amd64 openjdk:19 -- java --version # Nano Server racker --verbose run --rm --platform=windows/amd64 openjdk:19-nanoserver -- java --version diff --git a/postroj/winrunner.py b/postroj/winrunner.py index a60550f..a4b9a53 100644 --- a/postroj/winrunner.py +++ b/postroj/winrunner.py @@ -24,16 +24,16 @@ class WinRunner: - VCPUS = os.environ.get("RACKER_VM_VCPUS", 6) - MEMORY = os.environ.get("RACKER_VM_MEMORY", 6144) + VCPUS = os.environ.get("RACKER_WDM_VCPUS", 6) + MEMORY = os.environ.get("RACKER_WDM_MEMORY", 6144) def __init__(self, image: str): self.image_base = image self.image_real = "racker-runtime/" + self.image_base.replace("/", "-") - self.BOX = None - self.choose_box() - logger.info(f"Using host machine box image {self.BOX} for launching container image {self.image_base}") + self.wdm_machine = None + self.choose_wdm_machine() + logger.info(f"Using WDM host machine {self.wdm_machine} for launching container image {self.image_base}") self.workdir = Path(appdirs.user_state_dir(appname="racker")) self.workdir.mkdir(exist_ok=True, parents=True) @@ -58,9 +58,9 @@ def setup(self): else: logger.info(f"Windows Docker Machine already installed into {self.wdmdir}") - def choose_box(self): + def choose_wdm_machine(self): """ - Choose the right box based on the container image to launch. + Choose the right virtual machine based on the container image to launch. """ image = self.image_base @@ -69,8 +69,8 @@ def choose_box(self): logger.info(f"Inquiring information about OCI image '{image}'") - if "RACKER_VM_BOX" in os.environ: - self.BOX = os.environ["RACKER_VM_BOX"] + if "RACKER_WDM_MACHINE" in os.environ: + self.wdm_machine = os.environ["RACKER_WDM_MACHINE"] return # TODO: Cache the response from `skopeo inspect` to avoid the 3-second speed bump. @@ -103,12 +103,12 @@ def choose_box(self): "10.0.20348": "2022-box", } - for os_version, box in os_version_box_map.items(): + for os_version, machine in os_version_box_map.items(): if image_os_version.startswith(os_version): - self.BOX = box + self.wdm_machine = machine - if self.BOX is None: - raise ValueError(f"Unable to choose host machine box image for container image {image}, matching OS version {image_os_version}. " + if self.wdm_machine is None: + raise ValueError(f"Unable to choose WDM host machine for container image {image}, matching OS version {image_os_version}. " f"Please report this error to https://github.com/cicerops/racker/issues/new.") def start(self, provision=False): @@ -129,23 +129,23 @@ def start(self, provision=False): provision_option = "" if provision: provision_option = "--provision" - cmd(f"vagrant up --provider=virtualbox {provision_option} {self.BOX}", cwd=self.wdmdir, use_stderr=True) + cmd(f"vagrant up --provider=virtualbox {provision_option} {self.wdm_machine}", cwd=self.wdmdir, use_stderr=True) logger.info("Pinging Docker context") if not self.docker_context_online(): - raise IOError(f"Unable to bring up Docker context {self.BOX}") + raise IOError(f"Unable to bring up Docker context {self.wdm_machine}") # Attention: This can run into 60 second timeouts. # TODO: Use ``with stopit.ThreadingTimeout(timeout) as to_ctx_mgr``. # TODO: Make timeout values configurable. # https://github.com/moby/moby/blob/0e04b514fb/integration-cli/docker_cli_run_test.go - cmd(f"docker --context={self.BOX} ps", capture=True) + cmd(f"docker --context={self.wdm_machine} ps", capture=True) # Skip installing software using Chocolatey for specific Windows OS versions. # - Windows Nanoserver does not have PowerShell. # - Windows 2016 croaks like: # `The command 'cmd /S /C choco install --yes ...' returned a non-zero code: 3221225785` - if "nanoserver" in self.image_base or self.BOX == "2016-box": + if "nanoserver" in self.image_base or self.wdm_machine == "2016-box": self.image_real = self.image_base else: self.provision_image() @@ -163,7 +163,7 @@ def provision_image(self): logger.info(f"Provisioning Docker image for Windows environment based on {self.image_base}") dockerfile = pkg_resources.resource_filename("postroj", "winrunner.Dockerfile") tmpdir = tempfile.mkdtemp() - command = f"docker --context={self.BOX} build --platform=windows/amd64 " \ + command = f"docker --context={self.wdm_machine} build --platform=windows/amd64 " \ f"--file={dockerfile} --build-arg=BASE_IMAGE={self.image_base} --tag={self.image_real} {tmpdir}" logger.debug(f"Running command: {command}") try: @@ -188,7 +188,8 @@ def run(self, command, interactive: bool = False, tty: bool = False): if interactive or tty: option_interactive = "-it" - command = f"docker --context={self.BOX} run {option_interactive} --rm {self.image_real} {command}" + # TODO: Propagate ``--rm`` appropriately. + command = f"docker --context={self.wdm_machine} run {option_interactive} --rm {self.image_real} {command}" # When an interactive prompt is requested, spawn a shell without further ado. if interactive or tty: @@ -203,11 +204,11 @@ def docker_context_online(self): """ Test if the Docker context is online. """ - logger.info(f"Checking connectivity to Docker daemon in Windows context '{self.BOX}'") + logger.info(f"Checking connectivity to Docker daemon in Windows context '{self.wdm_machine}'") try: - response = cmd(f"docker context inspect {self.BOX}", capture=True) + response = cmd(f"docker context inspect {self.wdm_machine}", capture=True) except: - logger.warning(f"Docker context {self.BOX} not online or not created yet") + logger.warning(f"Docker context {self.wdm_machine} not online or not created yet") return False data = json.loads(response.stdout) From 218331739f522f0dc407acb1b98b59683cce2e89 Mon Sep 17 00:00:00 2001 From: Andreas Motl Date: Sat, 11 Jun 2022 23:47:24 +0200 Subject: [PATCH 08/14] [windows] Adjust resources for Windows Docker Machine The new default values are: - RACKER_WDM_VCPUS: 4 - RACKER_WDM_MEMORY: 4096 MB --- doc/winrunner.rst | 6 +++--- postroj/winrunner.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/winrunner.rst b/doc/winrunner.rst index 1fbcf4a..2144e79 100644 --- a/doc/winrunner.rst +++ b/doc/winrunner.rst @@ -65,13 +65,13 @@ Resources ========= When creating the `Windows Docker Machine`_ virtual machine, the program will -configure it to use 6 VCPUs and 6144 MB system memory by default. +configure it to use 4 VCPUs and 4 GB system memory by default. In order to adjust those values, use these environment variables before invoking the later commands:: - export RACKER_VM_VCPUS=12 - export RACKER_VM_MEMORY=8192 + export RACKER_WDM_VCPUS=8 + export RACKER_WDM_MEMORY=8192 If you want to adjust the values after the initial deployment, you will have to reset the `Windows Docker Machine`_ installation directory. For example, it is: diff --git a/postroj/winrunner.py b/postroj/winrunner.py index a4b9a53..29424ff 100644 --- a/postroj/winrunner.py +++ b/postroj/winrunner.py @@ -24,8 +24,8 @@ class WinRunner: - VCPUS = os.environ.get("RACKER_WDM_VCPUS", 6) - MEMORY = os.environ.get("RACKER_WDM_MEMORY", 6144) + VCPUS = os.environ.get("RACKER_WDM_VCPUS", 4) + MEMORY = os.environ.get("RACKER_WDM_MEMORY", 4096) def __init__(self, image: str): self.image_base = image From 0b1412a92f267e9ec97d4b6a735721fa1b04e408 Mon Sep 17 00:00:00 2001 From: Andreas Motl Date: Sun, 12 Jun 2022 00:02:49 +0200 Subject: [PATCH 09/14] [windows] Add environment variable `RACKER_WDM_PROVIDER` This can be used to reconfigure the Vagrant virtualization backend differently than VirtualBox. Possible values are, in alphabetical order, `hyperv`, `virtualbox`, `qemu`, `vmware_fusion`, `vmware_workstation`. It has been tested with `virtualbox` only. --- CHANGES.rst | 3 +++ doc/winrunner.rst | 24 ++++++++++++++++++++++-- postroj/winrunner.py | 3 ++- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 85b2391..31b530b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -22,6 +22,9 @@ in progress - Rename environment variables used to control the Windows Docker Machine subsystem. The new names are ``RACKER_WDM_VCPUS``, ``RACKER_WDM_MEMORY``, and ``RACKER_WDM_MACHINE``. +- Add environment variable ``RACKER_WDM_PROVIDER`` to reconfigure the + Vagrant virtualization backend differently than VirtualBox. + 2022-05-20 0.2.0 ================ diff --git a/doc/winrunner.rst b/doc/winrunner.rst index 2144e79..be13964 100644 --- a/doc/winrunner.rst +++ b/doc/winrunner.rst @@ -79,8 +79,26 @@ reset the `Windows Docker Machine`_ installation directory. For example, it is: - On Linux: ``/root/.local/state/racker/windows-docker-machine`` - On macOS: ``/Users/amo/Library/Application Support/racker/windows-docker-machine`` -Runtime -======= + +VM provider +=========== + +`Vagrant`_ is able to use different providers as virtualization backend. By +default, Racker selects `VirtualBox`_. In order to change the backend, +reconfigure this environment variable:: + + export RACKER_WDM_PROVIDER=vmware_workstation + +Possible values are, in alphabetical order, ``hyperv``, ``virtualbox``, +``qemu``, ``vmware_fusion``, ``vmware_workstation``. + +Please note that this has not been tested with providers other than +`VirtualBox`_, so we would welcome to receive feedback from the community +whether this also works well for them on other hypervisors. + + +VM machine +========== The architecture of Windows leads to container compatibility requirements that are different than on Linux, more background about this detail can be found at @@ -444,6 +462,8 @@ The whole software catalog can be inquired at `Chocolatey community packages`_. .. _Overview of System Center release options: https://docs.microsoft.com/en-us/system-center/ltsc-and-sac-overview .. _Python: https://www.python.org/ .. _Using Racker and Postroj for CrateDB CI: https://github.com/cicerops/racker/blob/main/doc/cratedb.rst +.. _Vagrant: https://www.vagrantup.com/ +.. _VirtualBox: https://www.virtualbox.org/ .. _Windows Container Base Images: https://docs.microsoft.com/en-us/virtualization/windowscontainers/manage-containers/container-base-images .. _Windows container version compatibility: https://docs.microsoft.com/en-us/virtualization/windowscontainers/deploy-containers/version-compatibility .. _Windows Docker Machine: https://github.com/StefanScherer/windows-docker-machine diff --git a/postroj/winrunner.py b/postroj/winrunner.py index 29424ff..0d86875 100644 --- a/postroj/winrunner.py +++ b/postroj/winrunner.py @@ -24,6 +24,7 @@ class WinRunner: + VAGRANT_PROVIDER = os.environ.get("RACKER_WDM_PROVIDER", "virtualbox") VCPUS = os.environ.get("RACKER_WDM_VCPUS", 4) MEMORY = os.environ.get("RACKER_WDM_MEMORY", 4096) @@ -129,7 +130,7 @@ def start(self, provision=False): provision_option = "" if provision: provision_option = "--provision" - cmd(f"vagrant up --provider=virtualbox {provision_option} {self.wdm_machine}", cwd=self.wdmdir, use_stderr=True) + cmd(f"vagrant up --provider={self.VAGRANT_PROVIDER} {provision_option} {self.wdm_machine}", cwd=self.wdmdir, use_stderr=True) logger.info("Pinging Docker context") if not self.docker_context_online(): From 33817da3da66732c35bdcf83f608322404a38304 Mon Sep 17 00:00:00 2001 From: Andreas Motl Date: Sun, 12 Jun 2022 02:13:32 +0200 Subject: [PATCH 10/14] [windows] Increase test coverage --- postroj/winrunner.py | 11 ++--- testing/postroj/test_winrunner.py | 60 +++++++++++++++++++++++++ testing/racker/test_run.py | 72 ++++++++++++++++++++++++++++++ testing/racker/test_run_windows.py | 7 ++- 4 files changed, 144 insertions(+), 6 deletions(-) create mode 100644 testing/postroj/test_winrunner.py diff --git a/postroj/winrunner.py b/postroj/winrunner.py index 0d86875..2bd9802 100644 --- a/postroj/winrunner.py +++ b/postroj/winrunner.py @@ -219,9 +219,9 @@ def docker_context_online(self): return port_is_up(address.hostname, address.port) -def hcmd(command, cwd=None, silent=False): +def hcmd(command, cwd=None, use_stderr=True, silent=False): logger.debug(f"Running command: {command}") - return cmd(command, cwd=cwd, use_stderr=True) + return cmd(command, cwd=cwd, use_stderr=use_stderr) def hshell(command, cwd=None): @@ -230,9 +230,10 @@ def hshell(command, cwd=None): return subprocess.check_output(command, shell=True, cwd=cwd).decode() -def ccmd(command, use_pty=False): +def ccmd(command, use_pty=False, capture=False): logger.debug(f"Running command: {command}") - p = cmd(command=command, use_pty=use_pty) + p = cmd(command=command, use_pty=use_pty, capture=capture) stdout = p.stdout - fix_tty() + if use_pty: + fix_tty() return stdout diff --git a/testing/postroj/test_winrunner.py b/testing/postroj/test_winrunner.py new file mode 100644 index 0000000..afe378b --- /dev/null +++ b/testing/postroj/test_winrunner.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# (c) 2022 Andreas Motl +import pytest + +from postroj.winrunner import ccmd, hshell, hcmd + + +def test_hcmd_echo_newline(capfd): + process = hcmd("/bin/echo foo", use_stderr=False) + assert process.stdout is None + assert process.stderr is None + + result = capfd.readouterr() + assert result.out == "foo\n" + assert result.err == "" + + +def test_hcmd_echo_no_newline(capfd): + hcmd("/bin/echo -n foo", use_stderr=False) + result = capfd.readouterr() + assert result.out == "foo" + + +def test_hshell_echo_newline(): + output = hshell("/bin/echo foo") + assert output == "foo\n" + + +def test_hshell_echo_no_newline(): + output = hshell("/bin/echo -n foo") + assert output == "foo" + + +def test_ccmd_echo_newline(): + output = ccmd("/bin/echo foo", capture=True) + assert output == "foo\n" + + +def test_ccmd_echo_newline_pty(capfd): + output = ccmd("/bin/echo foo", capture=True, use_pty=True) + assert output is None + + result = capfd.readouterr() + assert result.out == "foo\n" + assert result.err == "" + + +@pytest.mark.xfail +def test_ccmd_echo_no_newline(): + output = ccmd("/bin/echo -n foo", capture=True) + assert output == "foo" + + +def test_ccmd_echo_no_newline_pty(capfd): + output = ccmd("/bin/echo -n foo", capture=True, use_pty=True) + assert output is None + + result = capfd.readouterr() + assert result.out == "foo" + assert result.err == "" diff --git a/testing/racker/test_run.py b/testing/racker/test_run.py index 4edb12c..0af6a6c 100644 --- a/testing/racker/test_run.py +++ b/testing/racker/test_run.py @@ -1,9 +1,11 @@ # -*- coding: utf-8 -*- # (c) 2022 Andreas Motl +import re import shlex import subprocess import sys from pathlib import Path +from unittest import mock import pytest from click._compat import strip_ansi @@ -12,6 +14,9 @@ from racker.cli import cli +# Currently, this test module effectively runs well on Linux, +# because it invokes machinery based on `systemd-nspawn`. + if sys.platform != "linux": pytest.skip("Skipping Linux-only tests", allow_module_level=True) @@ -104,6 +109,73 @@ def test_run_stdin_stdout(monkeypatch, capsys, delay): assert process.stdout == b"foo" +@pytest.mark.xfail(reason="Not working within Linux VM on macOS. Reason: " + "Cannot enable nested VT-x/AMD-V without nested-paging and unrestricted guest execution!") +def test_run_windows_valid_image(capfd, delay): + """ + Request a valid Windows container image. + + Note: This is currently not possible, because somehow VT-x does not work + well enough to support this scenario, at least not on macOS Catalina. + """ + runner = CliRunner() + + result = runner.invoke(cli, "run --rm --platform=windows/amd64 mcr.microsoft.com/windows/nanoserver:ltsc2022 -- cmd /C ver", catch_exceptions=False) + assert result.exit_code == 0 + + captured = capfd.readouterr() + assert "Microsoft Windows [Version 10.0.20348.707]" in captured.out + + +def test_run_windows_invalid_image(caplog, delay): + """ + Request an invalid Windows container image and make sure it croaks correctly. + """ + runner = CliRunner() + + result = runner.invoke(cli, "run --rm --platform=windows/amd64 images.example.org/foo/bar:special -- cmd /C ver", catch_exceptions=False) + assert result.exit_code == 1 + + assert re.match(".*Inquiring information about OCI image .+ failed.*", caplog.text) + assert re.match(".*Reason:.*Error parsing image name .* (error )?pinging (container|docker) registry images.example.org.*", caplog.text) + + +def test_run_windows_mocked_noninteractive(): + """ + Pretend to launch a Windows container, but don't. + Reason: The `WinRunner` machinery has been mocked completely. + """ + runner = CliRunner() + + with mock.patch("racker.cli.WinRunner") as winrunner: + result = runner.invoke(cli, "run --rm --platform=windows/amd64 images.example.org/foo/bar:special -- cmd /C ver", catch_exceptions=False) + assert result.exit_code == 0 + assert winrunner.mock_calls == [ + mock.call(image='images.example.org/foo/bar:special'), + mock.call().setup(), + mock.call().start(), + mock.call().run('cmd /C ver', interactive=False, tty=False), + ] + + +def test_run_windows_mocked_interactive(): + """ + Pretend to launch a Windows container, but don't. + Reason: The `WinRunner` machinery has been mocked completely. + """ + runner = CliRunner() + + with mock.patch("racker.cli.WinRunner") as winrunner: + result = runner.invoke(cli, "run -it --rm --platform=windows/amd64 images.example.org/foo/bar:special -- cmd /C ver", catch_exceptions=False) + assert result.exit_code == 0 + assert winrunner.mock_calls == [ + mock.call(image='images.example.org/foo/bar:special'), + mock.call().setup(), + mock.call().start(), + mock.call().run('cmd /C ver', interactive=True, tty=True), + ] + + # Unfortunately, this fails. """ def test_run_stdin_stdout_original(capfd): diff --git a/testing/racker/test_run_windows.py b/testing/racker/test_run_windows.py index f3a09d0..370f359 100644 --- a/testing/racker/test_run_windows.py +++ b/testing/racker/test_run_windows.py @@ -11,8 +11,13 @@ import pytest +# Currently, this test module effectively runs well on macOS. +# The following snippet skips invocation on both VirtualBox +# and GitHub Actions. + if "rackerhost-debian11" in socket.gethostname(): - pytest.skip("Nested virtualization with VT-x fails on developer's macOS workstation", allow_module_level=True) + pytest.skip("Nested virtualization with VT-x fails within " + "VirtualBox environment on developer's macOS workstation", allow_module_level=True) if "GITHUB_ACTIONS" in os.environ: pytest.skip("Installing the Vagrant filesystem image for Windows " From 2f5720fe2e7ac7b2ab709318840ba85d1f965e45 Mon Sep 17 00:00:00 2001 From: Andreas Motl Date: Sun, 12 Jun 2022 14:38:57 +0200 Subject: [PATCH 11/14] [windows] Documentation: Add use case how to build a Python package This aims to build Python wheels for PyTables in a DIY manner. It uses Microsoft Visual C++ Build Tools 2015 and Anaconda, both installed using Chocolatey, and `cibuildwheel`. --- CHANGES.rst | 3 + doc/use-cases/python-on-windows.rst | 74 +++++++++++++++++++++++++ doc/use-cases/windows-pytables-wheel.sh | 64 +++++++++++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 doc/use-cases/python-on-windows.rst create mode 100755 doc/use-cases/windows-pytables-wheel.sh diff --git a/CHANGES.rst b/CHANGES.rst index 31b530b..7a228d3 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -24,6 +24,9 @@ in progress and ``RACKER_WDM_MACHINE``. - Add environment variable ``RACKER_WDM_PROVIDER`` to reconfigure the Vagrant virtualization backend differently than VirtualBox. +- Documentation: Add use case how to build a Python package within a + Windows environment, using Microsoft Visual C++ Build Tools 2015 and + Anaconda, both installed using Chocolatey, and ``cibuildwheel``. 2022-05-20 0.2.0 diff --git a/doc/use-cases/python-on-windows.rst b/doc/use-cases/python-on-windows.rst new file mode 100644 index 0000000..f9d2adb --- /dev/null +++ b/doc/use-cases/python-on-windows.rst @@ -0,0 +1,74 @@ +############################### +Use cases for Python on Windows +############################### + + +************************* +Build wheels for PyTables +************************* + +About +===== + +DIY, without a hosted CI provider. + +How to build a Python wheel package, here PyTables, within a Windows +environment, using Microsoft Visual C++ Build Tools 2015 and Anaconda, both +installed using Chocolatey, and ``cibuildwheel``. + +References +========== + +- https://github.com/PyTables/PyTables/pull/872#issuecomment-773535041 +- https://github.com/PyTables/PyTables/blob/master/.github/workflows/wheels.yml + +Synopsis +======== + +.. note:: + + The ``windows-pytables-wheel.sh`` program is part of this repository. You + will only find it at the designated location when running ``racker`` from + the working tree of its Git repository. + + You still can get hold of the program and invoke it, by downloading it from + `windows-pytables-wheel.sh`_. + +So, let's start by defining the download URL to that file:: + + export PAYLOAD_URL=https://raw.githubusercontent.com/cicerops/racker/windows/doc/use-cases/windows-pytables-wheel.sh + +Unattended:: + + time racker --verbose run --rm --platform=windows/amd64 python:3.9 -- \ + "sh -c 'wget ${PAYLOAD_URL}; sh windows-pytables-wheel.sh'" + +Or, interactively:: + + racker --verbose run -it --rm --platform=windows/amd64 python:3.9 -- bash + wget ${PAYLOAD_URL} + sh windows-pytables-wheel.sh + + +Future +====== + +See https://github.com/cicerops/racker/issues/8. + +When working on the code base, you can invoke the program directly from +the repository, after the ``--volume`` option got implemented:: + + # Unattended. + time racker --verbose run --rm \ + --volume=C:/Users/amo/dev/cicerops-foss/sources/postroj:C:/racker \ + --platform=windows/amd64 python:3.9 -- \ + sh /c/racker/doc/use-cases/windows-pytables-wheel.sh + + # Interactively. + racker --verbose run -it --rm \ + --volume=C:/Users/amo/dev/cicerops-foss/sources/postroj:C:/racker \ + --platform=windows/amd64 python:3.9 -- bash + /c/racker/doc/use-cases/windows-pytables-wheel.sh + + +.. _windows-pytables-wheel.sh: https://raw.githubusercontent.com/cicerops/racker/main/doc/use-cases/windows-pytables-wheel.sh diff --git a/doc/use-cases/windows-pytables-wheel.sh b/doc/use-cases/windows-pytables-wheel.sh new file mode 100755 index 0000000..f38beb0 --- /dev/null +++ b/doc/use-cases/windows-pytables-wheel.sh @@ -0,0 +1,64 @@ +#!/bin/bash +# +# Build wheels for PyTables on Windows. DIY, without a hosted CI provider. +# https://github.com/cicerops/racker/blob/main/doc/use-cases/python-on-windows.rst +# +# Synopsis:: +# +# racker --verbose run -it --rm --platform=windows/amd64 python:3.9 -- bash +# /c/racker/doc/use-cases/windows-pytables-wheel.sh +# +set -e + +# Install prerequisites. + +# Microsoft Visual C++ Build Tools 2015 14.0.25420.1 +# https://community.chocolatey.org/packages/microsoft-visual-cpp-build-tools +# Microsoft Build Tools 2015 (Install) +# https://community.chocolatey.org/packages/microsoft-build-tools-2015 +#choco install --yes microsoft-visual-cpp-build-tools --install-arguments="'/InstallSelectableItems Win81SDK_CppBuildSKUV1;VisualCppBuildTools_ATLMFC_SDK'" +choco install --yes microsoft-visual-cpp-build-tools --install-arguments="'/Full'" + +# Miniconda - A minimal installer for Anaconda. +# https://conda.io/miniconda.html +# https://community.chocolatey.org/packages/miniconda3 +choco install --yes miniconda3 --package-parameters="'/AddToPath:1'" + +# TODO: `/AddToPath:1` seems to not work, so adjust `$PATH` manually. +export PATH="$PATH:/c/Tools/miniconda3/condabin" + +# TODO: At least within Bash, just addressing `conda` does not work. +export conda="conda.bat" + +# cibuildwheel - Build Python wheels for all the platforms on CI with minimal configuration. +# https://cibuildwheel.readthedocs.io/ +pip install --upgrade cibuildwheel + +# Check prerequisites. +echo $PATH +$conda --version +# cibuildwheel --version + +# Acquire sources. +mkdir -p /c/src +cd /c/src +test ! -d PyTables && git clone https://github.com/PyTables/PyTables --recursive --depth=1 +cd PyTables + +# Pretend to be on a build matrix. +export MATRIX_ARCH=win_amd64 # win32 +export MATRIX_ARCH_SUBDIR=win-64 # win-32 + +# Configure cibuildwheel. +export CIBW_BUILD="cp36-${MATRIX_ARCH} cp37-${MATRIX_ARCH} cp38-${MATRIX_ARCH} cp39-${MATRIX_ARCH} cp310-${MATRIX_ARCH}" +export CIBW_BEFORE_ALL_WINDOWS="$conda create --yes --name=build && $conda activate build && $conda config --env --set subdir ${MATRIX_ARCH_SUBDIR} && $conda install --yes blosc bzip2 hdf5 lz4 lzo snappy zstd zlib" +export CIBW_ENVIRONMENT_WINDOWS='CONDA_PREFIX="C:\\Miniconda\\envs\\build" PATH="$PATH;C:\\Miniconda\\envs\\build\\Library\\bin"' +export CIBW_ENVIRONMENT="PYTABLES_NO_EMBEDDED_LIBS=true DISABLE_AVX2=true" +export CIBW_BEFORE_BUILD="pip install -r requirements.txt cython>=0.29.21 delvewheel" +export CIBW_REPAIR_WHEEL_COMMAND_WINDOWS="delvewheel repair -w {dest_dir} {wheel}" + +# Debugging. +# env + +# Build wheel. +cibuildwheel --platform=windows --output-dir=wheelhouse From c59c19334c2e4fbd8dd1187cffc27842c68f343e Mon Sep 17 00:00:00 2001 From: Andreas Motl Date: Wed, 15 Jun 2022 17:36:33 +0200 Subject: [PATCH 12/14] [windows] Prepare propagating the `--volume` option to the launcher --- doc/winrunner.rst | 2 ++ postroj/winrunner.py | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/doc/winrunner.rst b/doc/winrunner.rst index be13964..010c19d 100644 --- a/doc/winrunner.rst +++ b/doc/winrunner.rst @@ -180,6 +180,8 @@ different. - Windows Server: 4.8 GB - Windows: 7.1 GB +Around 2016/2019, it was like https://stefanscherer.github.io/windows-docker-workshop/#20. + System information ================== diff --git a/postroj/winrunner.py b/postroj/winrunner.py index 2bd9802..99584af 100644 --- a/postroj/winrunner.py +++ b/postroj/winrunner.py @@ -96,9 +96,11 @@ def choose_wdm_machine(self): raise ValueError(f"Container image {image} is not Windows, but {image_os_name} instead") # https://docs.microsoft.com/en-us/virtualization/windowscontainers/deploy-containers/version-compatibility + # https://stefanscherer.github.io/windows-docker-workshop/#91 os_version_box_map = { "10.0.14393": "2016-box", "10.0.16299": "2019-box", # openjdk:8-windowsservercore-1709 + "10.0.17134": "2019-box", "10.0.17763": "2019-box", "10.0.19042": "2022-box", "10.0.20348": "2022-box", @@ -189,8 +191,16 @@ def run(self, command, interactive: bool = False, tty: bool = False): if interactive or tty: option_interactive = "-it" - # TODO: Propagate ``--rm`` appropriately. - command = f"docker --context={self.wdm_machine} run {option_interactive} --rm {self.image_real} {command}" + # TODO: Propagate ``--rm`` option appropriately. + # TODO: Propagate ``--volume`` option. + # option_volume = "--volume=C:/Users/amo/dev/cicerops-foss/sources/postroj:C:/racker" + # https://github.com/cicerops/racker/issues/8 + option_volume = "" + #option_volume = "--volume=C:/Users/amo/dev/cicerops-foss/sources/postroj:C:/racker" + #option_volume = "--volume=C:/Users/amo/dev/panodata/sources/apprise:C:/apprise" + #option_volume = "--volume=C:/Users/amo/dev/earthobservations/wetterdienst:C:/wetterdienst" + #option_volume = "--volume=C:/Users/amo/dev/crate/sources/crate:C:/crate" + command = f"docker --context={self.wdm_machine} run {option_interactive} --rm {option_volume} {self.image_real} {command}" # When an interactive prompt is requested, spawn a shell without further ado. if interactive or tty: From 79c20e9131043872540eb69da35d2f4d27682148 Mon Sep 17 00:00:00 2001 From: Andreas Motl Date: Tue, 14 Feb 2023 09:19:33 +0100 Subject: [PATCH 13/14] [windows] Use Scoop package manager instead of Chocolatey --- doc/use-cases/windows-pytables-wheel.sh | 63 +++++++++++++++---------- doc/winrunner.rst | 12 +++-- postroj/winrunner.Dockerfile | 36 +++++++++++--- 3 files changed, 73 insertions(+), 38 deletions(-) diff --git a/doc/use-cases/windows-pytables-wheel.sh b/doc/use-cases/windows-pytables-wheel.sh index f38beb0..9842ce3 100755 --- a/doc/use-cases/windows-pytables-wheel.sh +++ b/doc/use-cases/windows-pytables-wheel.sh @@ -12,53 +12,64 @@ set -e # Install prerequisites. -# Microsoft Visual C++ Build Tools 2015 14.0.25420.1 -# https://community.chocolatey.org/packages/microsoft-visual-cpp-build-tools -# Microsoft Build Tools 2015 (Install) -# https://community.chocolatey.org/packages/microsoft-build-tools-2015 -#choco install --yes microsoft-visual-cpp-build-tools --install-arguments="'/InstallSelectableItems Win81SDK_CppBuildSKUV1;VisualCppBuildTools_ATLMFC_SDK'" -choco install --yes microsoft-visual-cpp-build-tools --install-arguments="'/Full'" - # Miniconda - A minimal installer for Anaconda. # https://conda.io/miniconda.html -# https://community.chocolatey.org/packages/miniconda3 -choco install --yes miniconda3 --package-parameters="'/AddToPath:1'" +scoop bucket add extras +scoop install miniconda3 +#export PATH="$PATH:/c/Users/ContainerAdministrator/scoop/apps/miniconda3/current/condabin" +#alias conda=conda.bat +#/c/Users/ContainerAdministrator/scoop/apps/miniconda3/current/condabin/conda.bat init bash +source /c/Users/ContainerAdministrator/scoop/apps/miniconda3/current/Scripts/activate +conda --version + +# /c/Users/ContainerAdministrator/scoop/apps/miniconda3/4.12.0/Scripts/conda.exe # TODO: `/AddToPath:1` seems to not work, so adjust `$PATH` manually. -export PATH="$PATH:/c/Tools/miniconda3/condabin" +#export PATH="$PATH:/c/Tools/miniconda3/condabin" # TODO: At least within Bash, just addressing `conda` does not work. -export conda="conda.bat" +#export conda="conda.bat" -# cibuildwheel - Build Python wheels for all the platforms on CI with minimal configuration. -# https://cibuildwheel.readthedocs.io/ -pip install --upgrade cibuildwheel # Check prerequisites. -echo $PATH -$conda --version +#echo $PATH +#$conda --version # cibuildwheel --version -# Acquire sources. -mkdir -p /c/src -cd /c/src -test ! -d PyTables && git clone https://github.com/PyTables/PyTables --recursive --depth=1 -cd PyTables - # Pretend to be on a build matrix. export MATRIX_ARCH=win_amd64 # win32 export MATRIX_ARCH_SUBDIR=win-64 # win-32 +# Activate and prepare Anaconda environment for building. +conda create --yes --name=build +conda activate build +conda config --env --set subdir ${MATRIX_ARCH_SUBDIR} + +# Install needed libraries. +conda install --yes blosc bzip2 hdf5 lz4 lzo snappy zstd zlib + +# Install cibuildwheel. +# Build Python wheels for all the platforms on CI with minimal configuration. +# https://cibuildwheel.readthedocs.io/ +pip install --upgrade cibuildwheel + # Configure cibuildwheel. -export CIBW_BUILD="cp36-${MATRIX_ARCH} cp37-${MATRIX_ARCH} cp38-${MATRIX_ARCH} cp39-${MATRIX_ARCH} cp310-${MATRIX_ARCH}" -export CIBW_BEFORE_ALL_WINDOWS="$conda create --yes --name=build && $conda activate build && $conda config --env --set subdir ${MATRIX_ARCH_SUBDIR} && $conda install --yes blosc bzip2 hdf5 lz4 lzo snappy zstd zlib" -export CIBW_ENVIRONMENT_WINDOWS='CONDA_PREFIX="C:\\Miniconda\\envs\\build" PATH="$PATH;C:\\Miniconda\\envs\\build\\Library\\bin"' +#export CIBW_BUILD="cp36-${MATRIX_ARCH} cp37-${MATRIX_ARCH} cp38-${MATRIX_ARCH} cp39-${MATRIX_ARCH} cp310-${MATRIX_ARCH}" +export CIBW_BUILD="cp39-${MATRIX_ARCH}" +#export CIBW_BEFORE_ALL_WINDOWS="conda create --yes --name=build && conda activate build && conda config --env --set subdir ${MATRIX_ARCH_SUBDIR} && conda install --yes blosc bzip2 hdf5 lz4 lzo snappy zstd zlib" +#export CIBW_ENVIRONMENT_WINDOWS='CONDA_PREFIX="C:\\Miniconda\\envs\\build" PATH="$PATH;C:\\Miniconda\\envs\\build\\Library\\bin"' export CIBW_ENVIRONMENT="PYTABLES_NO_EMBEDDED_LIBS=true DISABLE_AVX2=true" -export CIBW_BEFORE_BUILD="pip install -r requirements.txt cython>=0.29.21 delvewheel" +export CIBW_BEFORE_BUILD="echo $PATH; pip install -r requirements.txt cython>=0.29.21 delvewheel" export CIBW_REPAIR_WHEEL_COMMAND_WINDOWS="delvewheel repair -w {dest_dir} {wheel}" # Debugging. # env +# Acquire sources. +mkdir -p /c/src +cd /c/src +test ! -d PyTables && git clone https://github.com/PyTables/PyTables --recursive --depth=1 +cd PyTables + # Build wheel. cibuildwheel --platform=windows --output-dir=wheelhouse diff --git a/doc/winrunner.rst b/doc/winrunner.rst index 010c19d..03baf6f 100644 --- a/doc/winrunner.rst +++ b/doc/winrunner.rst @@ -8,17 +8,18 @@ About ***** Launch an interactive command prompt (cmd, PowerShell, or Bash) within a -Windows environment (2016, 2019, or 2022) or invoke programs +Windows environment (2016, 2019, or 2022), or invoke programs non-interactively. Features ======== - The subsystem is heavily based on the excellent `Windows Docker Machine`_. -- The package manager `Chocolatey`_ is pre-installed on the container images - where PowerShell is available. +- The `Scoop`_ package manager is pre-installed on the container images + where PowerShell is available. The `Chocolatey`_ package manager can be + installed on demand. - Programs like ``busybox``, ``curl``, ``git``, ``nano``, and ``wget`` are - pre-installed on the container images where `Chocolatey`_ is available. + pre-installed on the container images where `Scoop`_ is available. - The `Windows container version compatibility`_ problem is conveniently solved by automatically selecting the right machine matching the requested container image. @@ -190,7 +191,7 @@ Install and run `Winfetch`_:: racker --verbose run --rm \ --platform=windows/amd64 mcr.microsoft.com/windows/servercore:ltsc2022 -- \ - cmd /C 'choco install --yes --force winfetch & refreshenv & winfetch' + cmd /C 'scoop install winfetch & winfetch' .. figure:: https://user-images.githubusercontent.com/453543/173195228-b75c8727-7187-4c38-ae28-f74098dfb450.png :width: 800 @@ -463,6 +464,7 @@ The whole software catalog can be inquired at `Chocolatey community packages`_. .. _Midnight Commander: https://en.wikipedia.org/wiki/Midnight_Commander .. _Overview of System Center release options: https://docs.microsoft.com/en-us/system-center/ltsc-and-sac-overview .. _Python: https://www.python.org/ +.. _Scoop: https://scoop.sh/ .. _Using Racker and Postroj for CrateDB CI: https://github.com/cicerops/racker/blob/main/doc/cratedb.rst .. _Vagrant: https://www.vagrantup.com/ .. _VirtualBox: https://www.virtualbox.org/ diff --git a/postroj/winrunner.Dockerfile b/postroj/winrunner.Dockerfile index 05da7b3..6468d74 100644 --- a/postroj/winrunner.Dockerfile +++ b/postroj/winrunner.Dockerfile @@ -3,22 +3,44 @@ # # Provision a Windows operating system image. # -# - Install the FOSS version of the Chocolatey package manager. -# - Install additional software using Chocolatey. +# - Install the Scoop package manager. +# - Install additional software using Scoop. +# - https://scoop.sh/ # ARG BASE_IMAGE FROM ${BASE_IMAGE} -# Install the Chocolatey package manager. -RUN powershell -Command Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')) +# Restore the default Windows shell for correct batch processing. +SHELL ["cmd", "/S", "/C"] + +# Install the Scoop package manager. +# https://github.com/ScoopInstaller/Install#for-admin +RUN powershell -Command irm get.scoop.sh -outfile 'scoop-install.ps1'; .\scoop-install.ps1 -RunAsAdmin + +# Install/update Aria2 and Git first, to speed up downloads and have it up-to-date. +RUN scoop install aria2 git # Install essential and convenience programs. -RUN choco install --yes busybox curl nano wget -RUN choco install --yes git --package-parameters="'/GitAndUnixToolsOnPath /Editor:Nano'" +RUN scoop install msys2 zip unzip + +# Make MSYS2 programs available on the program search path. +# Note: It is not fully installed. In order to complete it, run `msys2` once. +RUN powershell $msys_path = $(scoop prefix msys2); [Environment]::SetEnvironmentVariable('Path', $env:Path + ';' + $msys_path + '\usr\bin', 'Machine') -# Rename Windows-native programs in favor of Chocolatey-installed/FOSS/GNU ones. +# Display the program search path. +#RUN powershell echo (Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' -Name Path).Path + +# Rename Windows-native programs in favor of Scoop-installed/FOSS/GNU ones. # An alternative would be to manipulate `$PATH`, but that is more tedious. RUN sh -c 'test -f /c/Windows/system32/curl && mv /c/Windows/system32/curl /c/Windows/system32/curl-win' RUN sh -c 'test -f /c/Windows/system32/convert && mv /c/Windows/system32/convert /c/Windows/system32/convert-ntfs' + +# TODO: With PowerShell 7, it is possible to remove the corresponding aliases. +#Remove-Alias -Name ls +#Remove-Alias -Name cat +#Remove-Alias -Name mv +#Remove-Alias -Name ps +#Remove-Alias -Name pwd +#Remove-Alias -Name rm From 26aee46ba21440a847586349ae3d0ec0aef6ac6f Mon Sep 17 00:00:00 2001 From: Andreas Motl Date: Tue, 14 Feb 2023 09:22:17 +0100 Subject: [PATCH 14/14] [windows] Update documentation --- doc/cratedb.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/doc/cratedb.rst b/doc/cratedb.rst index da05a46..ba8a923 100644 --- a/doc/cratedb.rst +++ b/doc/cratedb.rst @@ -26,6 +26,29 @@ Invoke a Java command prompt (JShell) with OpenJDK 18:: System.out.println("OS: " + System.getProperty("os.name") + ", version " + System.getProperty("os.version")) System.out.println("Java: " + System.getProperty("java.vendor") + ", version " + System.getProperty("java.version")) +Build CrateDB from source, extract Zip archive, and invoke available programs:: + + # Spawn a Windows environment with `cmd` shell. + # TODO: Bind-mounting not possible via command line yet, need to touch the code for this. + racker --verbose run --rm -it --platform=windows/amd64 eclipse-temurin:18-jdk -- cmd + + # Build CrateDB. + cd \crate + gradlew clean distZip + + # Extract zip archive. + mkdir \tmp + cd \tmp + unzip \crate\app\build\distributions\crate-5.3.0-SNAPSHOT-327070e3fe.zip + cd crate-5.3.0-SNAPSHOT-327070e3fe + + # Run CrateDB and tools. + bin\crate + # Submit to stop + + bin\crate-node fix-metadata + # Send y to the prompt + **************** postroj pkgprobe