diff --git a/docs/get-started/dgx-station-preparation.mdx b/docs/get-started/dgx-station-preparation.mdx index a231425b403..c1f754077b8 100644 --- a/docs/get-started/dgx-station-preparation.mdx +++ b/docs/get-started/dgx-station-preparation.mdx @@ -54,6 +54,12 @@ The installer records the override in the printed relogin command when Docker-gr On the generic Ubuntu path, accepting express install prepares the host with NVIDIA open driver `610.43.02`, Docker CE `29.6.1` with Buildx, and NVIDIA Container Toolkit `1.19.1`. Preparation probes package and runtime state first, reuses exact matches, and installs only missing pinned packages, including the NVIDIA Container Toolkit libraries and `nvidia-ctk` CLI. It permits only the reviewed factory transition from `dkms` `3.0.11-1ubuntu13` to `1:3.4.0-1ubuntu1`. +It also accepts an installed `dkms` `1:3.4.1-1ubuntu1` as a retained-compatible forward revision, warns that it differs from the validated `1:3.4.0-1ubuntu1` pin, excludes it from APT transactions, and continues runtime validation. +Retention requires `dpkg` to report a fully installed package for `arm64` or architecture `all`; an unhealthy or malformed record, an unexpected architecture, or a package-query failure stops preparation before APT changes. +The installer accepts the retained revision only when every package pin in the active generic-Ubuntu tuple matches its corresponding value in the qualified tuple. +NemoClaw DGX Station maintainers own this retained-version allowlist. +Any package pin change invalidates retention until maintainers requalify the tuple. +Maintainers remove a retained revision when runtime validation no longer passes. After reboot, preparation enables NVIDIA's packaged CDI refresh path and service, requires the `nvidia.com/gpu=all` device, and verifies it with a real container launch. If the packaged refresh fails or does not produce that device, preparation prints service diagnostics and stops for administrator repair. diff --git a/scripts/prepare-dgx-station-host.sh b/scripts/prepare-dgx-station-host.sh index 34dacd23f90..81054988493 100755 --- a/scripts/prepare-dgx-station-host.sh +++ b/scripts/prepare-dgx-station-host.sh @@ -36,6 +36,13 @@ readonly TOOLKIT_VERSION="1.19.1" readonly STATION_PACKAGE_ARCH="arm64" readonly FACTORY_DKMS_VERSION="3.0.11-1ubuntu13" readonly TARGET_DKMS_VERSION="1:3.4.0-1ubuntu1" +# NemoClaw DGX Station maintainers own this allowlist. The qualified tuple below +# binds each retained revision to the complete generic-Ubuntu package contract. +# Update both only after requalification; remove an entry when runtime +# validation no longer passes. Any PACKAGE_SPECS change invalidates retention. +readonly -a RETAINED_DKMS_VERSIONS=( + "1:3.4.1-1ubuntu1" +) # Keep this as a plain Ubuntu image: NVIDIA Container Toolkit injects the host # driver utility when CDI or --gpus is requested. This intentionally exercises # the documented runtime contract instead of relying on a CUDA image payload: @@ -66,6 +73,20 @@ readonly -a PACKAGE_SPECS=( "nvidia-container-toolkit-base=1.19.1-1" ) +readonly -a RETAINED_DKMS_QUALIFIED_PACKAGE_SPECS=( + "dkms=1:3.4.0-1ubuntu1" + "nvidia-driver-pinning-610=610-2ubuntu1" + "nvidia-driver-open=610.43.02-1ubuntu1" + "containerd.io=2.2.6-1~ubuntu.24.04~noble" + "docker-buildx-plugin=0.35.0-1~ubuntu.24.04~noble" + "docker-ce=5:29.6.1-1~ubuntu.24.04~noble" + "docker-ce-cli=5:29.6.1-1~ubuntu.24.04~noble" + "libnvidia-container-tools=1.19.1-1" + "libnvidia-container1=1.19.1-1" + "nvidia-container-toolkit=1.19.1-1" + "nvidia-container-toolkit-base=1.19.1-1" +) + readonly -a BASEOS_PACKAGE_SPECS=( "dgx-release=7.5.0" "dgx-repo=25.10-2" @@ -602,6 +623,23 @@ package_is_exact() { [[ "$(package_state "$1")" == "exact" ]] } +retained_dkms_policy_is_current() { + local index + ((${#PACKAGE_SPECS[@]} == ${#RETAINED_DKMS_QUALIFIED_PACKAGE_SPECS[@]})) || return 1 + for index in "${!PACKAGE_SPECS[@]}"; do + [[ "${PACKAGE_SPECS[$index]}" == "${RETAINED_DKMS_QUALIFIED_PACKAGE_SPECS[$index]}" ]] || return 1 + done +} + +dkms_version_is_retained() { + local actual=$1 retained + retained_dkms_policy_is_current || return 1 + for retained in "${RETAINED_DKMS_VERSIONS[@]}"; do + [[ "$actual" == "$retained" ]] && return 0 + done + return 1 +} + package_state() { local spec=$1 local name expected record query_status status architecture actual extra @@ -634,6 +672,8 @@ package_state() { printf 'wrong-architecture\n' elif [[ "$actual" == "$expected" ]]; then printf 'exact\n' + elif [[ "$name" == "dkms" && "$expected" == "$TARGET_DKMS_VERSION" ]] && dkms_version_is_retained "$actual"; then + printf 'retained-compatible\n' elif [[ "$name" == "dkms" && "$actual" == "$FACTORY_DKMS_VERSION" && "$expected" == "$TARGET_DKMS_VERSION" ]]; then printf 'approved-transition\n' else @@ -641,6 +681,23 @@ package_state() { fi } +warn_retained_package_version() { + local spec=$1 state name expected actual + state="$(package_state "$spec")" + [[ "$state" == "retained-compatible" ]] || return 0 + name="$(package_name "$spec")" + expected="$(package_expected_version "$spec")" + actual="$(installed_version "$name")" + warn "package=${name} status=retained_compatible actual=${actual} validated=${expected} decision=retain" +} + +warn_retained_package_versions() { + local spec + for spec in "${PACKAGE_SPECS[@]}"; do + warn_retained_package_version "$spec" + done +} + assert_no_package_mismatches() { local spec state name expected actual rejected=0 for spec in "${PACKAGE_SPECS[@]}"; do @@ -648,7 +705,7 @@ assert_no_package_mismatches() { name="$(package_name "$spec")" expected="$(package_expected_version "$spec")" case "$state" in - exact | missing) ;; + exact | retained-compatible | missing) ;; approved-transition) actual="$(installed_version "$name")" info "package=${name} status=approved_transition actual=${actual} expected=${expected}" @@ -669,13 +726,19 @@ assert_no_package_mismatches() { esac done ((rejected == 0)) \ - || fatal "Station prerequisite package state is unhealthy or differs from the validated pins; repair dpkg status or architecture issues before retrying" + || fatal "Station prerequisite package state is unhealthy or differs from the validated pins, retained-compatible versions, or approved factory transition; repair dpkg status or architecture issues before retrying" +} + +package_is_ready() { + local state + state="$(package_state "$1")" + [[ "$state" == "exact" || "$state" == "retained-compatible" ]] } -all_packages_exact() { +all_packages_ready() { local spec for spec in "${PACKAGE_SPECS[@]}"; do - package_is_exact "$spec" || return 1 + package_is_ready "$spec" || return 1 done return 0 } @@ -1133,7 +1196,7 @@ check_failed_units() { return 0 fi for unit in "${units[@]}"; do - if is_driver_transitional_unit "$unit" && all_packages_exact && ! driver_loaded_exact; then + if is_driver_transitional_unit "$unit" && all_packages_ready && ! driver_loaded_exact; then warn "driver unit failure allowed only until post-reboot verification: ${unit}" elif is_preparation_critical_unit "$unit"; then warn "failed preparation-critical unit: ${unit}" @@ -1291,6 +1354,9 @@ print_package_status() { actual="$(installed_version "$name")" info "package=${name} status=approved_transition actual=${actual} expected=${expected}" ;; + retained-compatible) + warn_retained_package_version "$spec" + ;; mismatch) actual="$(installed_version "$name")" warn "package=${name} status=mismatch actual=${actual} expected=${expected}" @@ -1527,7 +1593,7 @@ collect_package_transaction_specs() { state="$(package_state "$spec")" case "$state" in missing | approved-transition) PACKAGE_TRANSACTION_SPECS+=("$spec") ;; - exact) ;; + exact | retained-compatible) ;; *) fatal "Package transaction contains an unapproved prerequisite state: ${spec} (${state})" ;; esac done @@ -1823,11 +1889,11 @@ install_packages() { local spec for spec in "${PACKAGE_SPECS[@]}"; do - package_is_exact "$spec" || fatal "Installed package does not match ${spec}" + package_is_ready "$spec" || fatal "Installed package is outside the accepted state for ${spec}" done restore_packagekit_after_transaction \ || fatal "Could not restore PackageKit after Station package preparation" - info "pinned_packages=installed" + info "prerequisite_packages=ready" } ensure_docker_group() { @@ -2157,7 +2223,7 @@ verify_dgx_os_runtime_user() { verify_apply_state() { local spec for spec in "${PACKAGE_SPECS[@]}"; do - package_is_exact "$spec" || fatal "Package verification failed: ${spec}" + package_is_ready "$spec" || fatal "Package verification failed: ${spec}" done verify_gpu systemctl is-active --quiet nvidia-persistenced.service || fatal "nvidia-persistenced.service is not active" @@ -2225,7 +2291,7 @@ verify_gpu() { verify_host() { local spec user_name=${SUDO_USER:-$USER} for spec in "${PACKAGE_SPECS[@]}"; do - package_is_exact "$spec" || fatal "Package verification failed: ${spec}" + package_is_ready "$spec" || fatal "Package verification failed: ${spec}" done verify_gpu systemctl is-active --quiet nvidia-persistenced.service || fatal "nvidia-persistenced.service is not active" @@ -2252,14 +2318,14 @@ run_check() { fi print_package_status assert_no_package_mismatches - if all_packages_exact; then + if all_packages_ready; then if install_boot_marker_matches_current_boot; then warn "Package installation completed in the current boot; reboot is required" info "CHECK_RESULT=REBOOT_REQUIRED" elif driver_loaded_exact; then info "CHECK_RESULT=PACKAGES_AND_DRIVER_PRESENT" else - warn "Exact packages are installed but driver ${DRIVER_VERSION} is not loaded; reboot is required" + warn "Accepted prerequisite packages are installed but driver ${DRIVER_VERSION} is not loaded; reboot is required" info "CHECK_RESULT=REBOOT_REQUIRED" fi else @@ -2301,16 +2367,17 @@ run_apply() { require_command grep require_command readlink require_command sha256sum + warn_retained_package_versions if reboot_required; then - if all_packages_exact && ! driver_loaded_exact; then + if all_packages_ready && ! driver_loaded_exact; then warn "A reboot is required before runtime setup can continue" exit_reboot_required fi fatal "An unrelated reboot is already pending" fi - if ! all_packages_exact; then + if ! all_packages_ready; then assert_no_package_mismatches install_packages ensure_docker_group @@ -2327,7 +2394,7 @@ run_apply() { fi driver_loaded_exact || { - warn "Pinned packages are installed but driver ${DRIVER_VERSION} is not loaded" + warn "Accepted prerequisite packages are installed but driver ${DRIVER_VERSION} is not loaded" exit_reboot_required } @@ -2353,7 +2420,8 @@ run_verify() { verify_dgx_os_runtime_user return 0 fi - all_packages_exact || fatal "Pinned prerequisite packages are incomplete; run --apply" + warn_retained_package_versions + all_packages_ready || fatal "Accepted prerequisite packages are incomplete; run --apply" driver_loaded_exact || fatal "Pinned driver is not loaded; reboot, then run --apply" verify_host } diff --git a/test/install-station-container-coexistence.test.ts b/test/install-station-container-coexistence.test.ts index 7caa5f7b21d..ab10619fe63 100644 --- a/test/install-station-container-coexistence.test.ts +++ b/test/install-station-container-coexistence.test.ts @@ -111,7 +111,7 @@ check_dpkg_database_health() { :; } check_failed_units() { :; } check_agent_and_inference_conflicts() { :; } driver_loaded_exact() { return 0; } -package_is_exact() { return 0; } +package_is_ready() { return 0; } verify_gpu() { :; } systemctl() { case "$*" in @@ -186,7 +186,7 @@ require_docker_mutation_quiescence "refreshing NVIDIA CDI configuration" name: "pending prerequisite", setup: ` reboot_required() { return 0; } -all_packages_exact() { return 0; } +all_packages_ready() { return 0; } driver_loaded_exact() { return 1; } `, expectedGate: "REBOOT_HANDOFF_BLOCKED check=1", @@ -195,7 +195,7 @@ driver_loaded_exact() { return 1; } name: "post-install", setup: ` reboot_required() { return 1; } -all_packages_exact() { return 1; } +all_packages_ready() { return 1; } require_docker_restart_quiescence() { local checks checks="$(cat "$HOME/reboot-gate-checks" 2>/dev/null || printf '0')" @@ -212,7 +212,7 @@ require_docker_restart_quiescence() { name: "same-boot marker", setup: ` reboot_required() { return 1; } -all_packages_exact() { return 0; } +all_packages_ready() { return 0; } install_boot_marker_matches_current_boot() { return 0; } `, expectedGate: "REBOOT_HANDOFF_BLOCKED check=1", @@ -221,7 +221,7 @@ install_boot_marker_matches_current_boot() { return 0; } name: "unloaded driver", setup: ` reboot_required() { return 1; } -all_packages_exact() { return 0; } +all_packages_ready() { return 0; } install_boot_marker_matches_current_boot() { return 1; } driver_loaded_exact() { return 1; } `, @@ -231,7 +231,7 @@ driver_loaded_exact() { return 1; } name: "Docker group", setup: ` reboot_required() { return 1; } -all_packages_exact() { return 0; } +all_packages_ready() { return 0; } install_boot_marker_matches_current_boot() { return 1; } driver_loaded_exact() { return 0; } finish_runtime() { DOCKER_GROUP_ADDED=1; } diff --git a/test/install-station-host-preparation.test.ts b/test/install-station-host-preparation.test.ts index 339d344d0bb..9250a5fa1af 100644 --- a/test/install-station-host-preparation.test.ts +++ b/test/install-station-host-preparation.test.ts @@ -293,7 +293,7 @@ check_failed_units common_preflight() { :; } require_command() { :; } acquire_sudo() { :; } -all_packages_exact() { return 0; } +all_packages_ready() { return 0; } install_boot_marker_matches_current_boot() { return 1; } driver_loaded_exact() { return 0; } install_packages() { printf 'INSTALL_PACKAGES\n'; } @@ -317,7 +317,7 @@ run_apply common_preflight() { :; } require_command() { :; } acquire_sudo() { :; } -all_packages_exact() { return 1; } +all_packages_ready() { return 1; } installed_package_record() { if [[ "$1" == "dkms" ]]; then printf 'ii |all|3.0.11-1ubuntu13'; else return 1; fi } @@ -352,6 +352,7 @@ validate_package_availability() { printf 'VALIDATE_PACKAGES\n'; } simulate_install() { printf 'SIMULATE_INSTALL\n'; } require_docker_restart_quiescence() { printf 'RECHECK_RESTART_QUIESCENCE\n'; } package_state() { printf 'missing\n'; } +package_is_ready() { return 0; } package_is_exact() { return 0; } assert_package_transaction_ready() { printf 'PACKAGE_TRANSACTION_READY %s\n' "$1"; } check_dpkg_database_health() { printf 'DPKG_AUDIT_CLEAN\n'; } @@ -377,7 +378,7 @@ install_packages ]) { expect(output).toContain(spec); } - expect(output).toContain("pinned_packages=installed"); + expect(output).toContain("prerequisite_packages=ready"); }); it("does not refresh CDI when the GPU launch probe already passes", () => { const { result, output } = runSourced( @@ -559,7 +560,7 @@ assert_root_directory_safe /etc/apt/keyrings test_directory common_preflight() { :; } require_command() { :; } acquire_sudo() { :; } -all_packages_exact() { return 0; } +all_packages_ready() { return 0; } install_boot_marker_matches_current_boot() { return 1; } driver_loaded_exact() { return 0; } finish_runtime() { DOCKER_GROUP_ADDED=1; printf 'FINISH_RUNTIME\n'; } @@ -831,7 +832,7 @@ main "$READ_MODE" ` common_preflight() { :; } require_command() { :; } -all_packages_exact() { return 0; } +all_packages_ready() { return 0; } driver_loaded_exact() { return 1; } run_verify `, diff --git a/test/install-station-package-transaction.test.ts b/test/install-station-package-transaction.test.ts index 8ffccb39f7f..798c119fa40 100644 --- a/test/install-station-package-transaction.test.ts +++ b/test/install-station-package-transaction.test.ts @@ -26,7 +26,11 @@ const EXPECTED_PACKAGE_SPECS = [ const DOCKER_CE_SPEC = "docker-ce=5:29.6.1-1~ubuntu.24.04~noble"; const DKMS_SPEC = "dkms=1:3.4.0-1ubuntu1"; -function runSourced(body: string, extraEnv: NodeJS.ProcessEnv = {}) { +function runSourced( + body: string, + extraEnv: NodeJS.ProcessEnv = {}, + scriptUnderTest = STATION_PREPARE, +) { const home = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-station-package-transaction-")); const result = spawnSync( "bash", @@ -37,7 +41,7 @@ function runSourced(body: string, extraEnv: NodeJS.ProcessEnv = {}) { env: { HOME: home, PATH: TEST_SYSTEM_PATH, - SCRIPT_UNDER_TEST: STATION_PREPARE, + SCRIPT_UNDER_TEST: scriptUnderTest, ...extraEnv, }, timeout: 15_000, @@ -72,6 +76,123 @@ validate_apt_preinstall_plan "$HOME/targets" <<<"$APT_PLAN" } describe("DGX Station package transaction", () => { + it("warns and retains the qualified DKMS forward revision for its package tuple (#7211)", () => { + const { result, output } = runSourced( + ` +installed_version() { + if [[ "$1" == "dkms" ]]; then printf '%s' "$DKMS_ACTUAL"; fi +} +installed_package_record() { + if [[ "$1" == "dkms" ]]; then printf 'ii |all|%s' "$DKMS_ACTUAL"; else return 1; fi +} +printf 'state=' +package_state 'dkms=1:3.4.0-1ubuntu1' +package_is_ready 'dkms=1:3.4.0-1ubuntu1' +warn_retained_package_version 'dkms=1:3.4.0-1ubuntu1' +`, + { DKMS_ACTUAL: "1:3.4.1-1ubuntu1" }, + ); + + expect(result.status, output).toBe(0); + expect(output).toContain("state=retained-compatible"); + expect(output).toContain( + "package=dkms status=retained_compatible actual=1:3.4.1-1ubuntu1 validated=1:3.4.0-1ubuntu1 decision=retain", + ); + }); + + it("rejects an unlisted DKMS revision (#7211)", () => { + const { result, output } = runSourced( + ` +installed_version() { + if [[ "$1" == "dkms" ]]; then printf '%s' "$DKMS_ACTUAL"; fi +} +installed_package_record() { + if [[ "$1" == "dkms" ]]; then printf 'ii |all|%s' "$DKMS_ACTUAL"; else return 1; fi +} +printf 'state=' +package_state 'dkms=1:3.4.0-1ubuntu1' +if package_is_ready 'dkms=1:3.4.0-1ubuntu1'; then + printf 'ready=yes\n' +else + printf 'ready=no\n' +fi +`, + { DKMS_ACTUAL: "1:3.4.2-1ubuntu1" }, + ); + + expect(result.status, output).toBe(0); + expect(output).toContain("state=mismatch"); + expect(output).toContain("ready=no"); + }); + + it("rejects retained DKMS after a companion package pin changes (#7211)", () => { + const source = fs.readFileSync(STATION_PREPARE, "utf-8"); + const qualifiedTuple = `readonly -a RETAINED_DKMS_QUALIFIED_PACKAGE_SPECS=(\n${EXPECTED_PACKAGE_SPECS.map((spec) => ` "${spec}"`).join("\n")}\n)`; + const staleQualifiedTuple = qualifiedTuple.replace( + DOCKER_CE_SPEC, + "docker-ce=5:29.6.0-1~ubuntu.24.04~noble", + ); + const stalePolicySource = source.replace(qualifiedTuple, staleQualifiedTuple); + expect(stalePolicySource).not.toBe(source); + + const fixtureDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-station-stale-policy-")); + const stalePolicyScript = path.join(fixtureDir, "prepare-dgx-station-host.sh"); + fs.writeFileSync(stalePolicyScript, stalePolicySource); + try { + const { result, output } = runSourced( + ` +installed_version() { + if [[ "$1" == "dkms" ]]; then printf '%s' "$DKMS_ACTUAL"; fi +} +installed_package_record() { + if [[ "$1" == "dkms" ]]; then printf 'ii |all|%s' "$DKMS_ACTUAL"; else return 1; fi +} +printf 'state=' +package_state 'dkms=1:3.4.0-1ubuntu1' +if package_is_ready 'dkms=1:3.4.0-1ubuntu1'; then + printf 'ready=yes\n' +else + printf 'ready=no\n' +fi +`, + { DKMS_ACTUAL: "1:3.4.1-1ubuntu1" }, + stalePolicyScript, + ); + + expect(result.status, output).toBe(0); + expect(output).toContain("state=mismatch"); + expect(output).toContain("ready=no"); + } finally { + fs.rmSync(fixtureDir, { recursive: true, force: true }); + } + }); + + it("retains qualified DKMS without entering package installation (#7211)", () => { + const { result, output } = runSourced(` +common_preflight() { :; } +require_command() { :; } +acquire_sudo() { :; } +package_state() { + if [[ "$1" == dkms=* ]]; then printf 'retained-compatible\n'; else printf 'exact\n'; fi +} +installed_version() { + if [[ "$1" == "dkms" ]]; then printf '1:3.4.1-1ubuntu1'; fi +} +install_boot_marker_matches_current_boot() { return 1; } +driver_loaded_exact() { return 0; } +install_packages() { printf 'INSTALL_PACKAGES\n'; } +finish_runtime() { printf 'FINISH_RUNTIME\n'; } +verify_apply_state() { printf 'VERIFY_APPLY_STATE\n'; } +run_apply +`); + + expect(result.status, output).toBe(0); + expect(output).toContain("status=retained_compatible"); + expect(output).toContain("decision=retain"); + expect(output).not.toContain("INSTALL_PACKAGES"); + expect(output).toContain("APPLY_RESULT=COMPLETE"); + }); + it("passes the complete pinned tuple when every package is missing", () => { const { result, output } = runSourced(` configure_repositories() { printf 'CONFIGURE_REPOSITORIES\n'; } @@ -91,6 +212,7 @@ apt-get() { } require_docker_restart_quiescence() { printf 'RECHECK_DOCKER_RESTART %s\n' "$1"; } package_state() { printf 'missing\n'; } +package_is_ready() { return 0; } package_is_exact() { return 0; } assert_package_transaction_ready() { printf 'PACKAGE_TRANSACTION_READY %s\n' "$1"; } check_dpkg_database_health() { printf 'DPKG_AUDIT_CLEAN\n'; } @@ -137,11 +259,24 @@ cat "$HOME/apt-cache-calls" expect(output).toContain(quiescenceMarker); expect(output.indexOf(installMarker)).toBeGreaterThan(output.indexOf(quiescenceMarker)); expect(output).toContain("CLEANUP_GUARD"); - expect(output).toContain("pinned_packages=installed"); + expect(output).toContain("prerequisite_packages=ready"); }); - it("excludes retained exact packages from every APT transaction command", () => { - const retainedSpec = "docker-ce=5:29.6.1-1~ubuntu.24.04~noble"; + it.each([ + { + label: "exact", + retainedSpec: "docker-ce=5:29.6.1-1~ubuntu.24.04~noble", + retainedState: "exact", + }, + { + label: "qualified", + retainedSpec: DKMS_SPEC, + retainedState: "retained-compatible", + }, + ])("excludes $label retained packages from every APT transaction command (#7211)", ({ + retainedSpec, + retainedState, + }) => { const missingSpecs = EXPECTED_PACKAGE_SPECS.filter((spec) => spec !== retainedSpec); const { result, output } = runSourced(` configure_repositories() { :; } @@ -161,8 +296,9 @@ apt-get() { } require_docker_restart_quiescence() { :; } package_state() { - if [[ "$1" == '${retainedSpec}' ]]; then printf 'exact\n'; else printf 'missing\n'; fi + if [[ "$1" == '${retainedSpec}' ]]; then printf '${retainedState}\n'; else printf 'missing\n'; fi } +package_is_ready() { return 0; } package_is_exact() { return 0; } assert_package_transaction_ready() { :; } check_dpkg_database_health() { :; } @@ -218,6 +354,7 @@ package_state() { if [[ "$1" == docker-ce=* ]]; then printf 'missing\n'; else printf 'exact\n'; fi } installed_version() { if [[ "$1" == "libc6" ]]; then printf '2.39-0ubuntu8'; fi; } +package_is_ready() { return 0; } package_is_exact() { return 0; } assert_package_transaction_ready() { :; } check_dpkg_database_health() { :; }